Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/user notification settings #35

Merged
merged 10 commits into from
Dec 10, 2024
272 changes: 272 additions & 0 deletions src/api/userNotificationSettings/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import express, { NextFunction, Request, Response } from 'express';

import {
UserNotificationSettings,
UserNotificationSettingsCrud,
} from './notificationSettings.crud';
import { CreateNotificcationSettingsSchema } from './validation.schema';

import { ErrorMessages } from '~/core/dictionary/error.messages';
import {
BadRequestError,
UnauthorizedError,
UnprocessableEntityError,
} from '~/core/errors';
import { logger } from '~/core/logger';
import { authMiddleware } from '~/core/middleware/auth';
import { modelToPlain } from '~/core/utils';
import { isAuthenticated } from '~/shared/user';

const route = express.Router();

/**
* @swagger
* /api/protected/userNotificationSettings/:
* get:
* summary: Retrieve user notification settings
* tags: [userNotificationSettings]
* security:
* - bearerAuth: [] # Indicates that authentication is required
* responses:
* 200:
* description: Successfully retrieved user notification settings
* content:
* application/json:
* schema:
* type: object
* properties:
* id:
* type: string
* description: Unique ID of the user notification settings record
* example: "notificationSettings-id"
* userId:
* type: string
* description: ID of the user
* example: "user-id"
* dailyReminder:
* type: boolean
* description: Indicates whether the user enabled daily reminder
* example: true
* createdAt:
* type: string
* format: date-time
* description: When the notification settings was created
* example: "2024-01-01T12:00:00Z"
* updatedAt:
* type: string
* format: date-time
* description: When the notification settings was last updated
* example: "2024-01-02T12:00:00Z"
* 400:
* description: User notification settings does not exist
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: BAD_REQUEST
* statusCode:
* type: integer
* example: 400
* message:
* type: string
* example: User notification settings entity does not exist
* 401:
* description: Unauthorized - User is not authenticated
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: ERROR
* statusCode:
* type: integer
* example: 401
* message:
* type: string
* example: Unauthorized
* 500:
* description: Internal server error
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: SERVER_ERROR
* statusCode:
* type: integer
* example: 500
* message:
* type: string
* example: Internal server error
*/
route.get(
'/',
authMiddleware,
async (req: Request, res: Response, next: NextFunction) => {
const user = req.user;

if (!isAuthenticated(user)) {
return next(new UnauthorizedError(ErrorMessages.unauthorized));
}

logger.info(
`[${req.traceId}] Get user notification settings started by: ${user.email}`,
);

const userNotificationSettingsEntity =
await UserNotificationSettingsCrud.getNotificationSettingsByUserId(
user.id,
);

if (!userNotificationSettingsEntity) {
throw new BadRequestError(
`User notification settings entity does not exist`,
);
}

const userNotificationSettings = modelToPlain<UserNotificationSettings>(
userNotificationSettingsEntity,
);

res.status(200).json(userNotificationSettings);
},
);

/**
* @swagger
* /api/protected/userNotificationSettings/save:
* post:
* summary: Save user notification settings
* tags: [userNotificationSettings]
* security:
* - bearerAuth: [] # Indicates that authentication is required
* requestBody:
* required: true
* description: Notification settings of the user to be saved
* content:
* application/json:
* schema:
* type: object
* properties:
* dailyReminder:
* type: boolean
* description: Indicates whether the user enabled daily reminder
* example: true
* responses:
* 200:
* description: User notification settings successfully saved
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* 401:
* description: Unauthorized - User is not authenticated
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: ERROR
* statusCode:
* type: integer
* example: 401
* message:
* type: string
* example: Unauthorized
* 422:
* description: Invalid data in the request body
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: ERROR
* statusCode:
* type: integer
* example: 422
* message:
* type: string
* example: "Invalid data provided"
* 404:
* description: Notification settings saving failed or user not found
* content:
* application/json:
* schema:
* type: object
* properties:
* isSuccess:
* type: boolean
* example: false
* error:
* type: string
* example: "Error saving user notification settings"
* 500:
* description: Internal server error
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: SERVER_ERROR
* statusCode:
* type: integer
* example: 500
* message:
* type: string
* example: Internal server error
*/
route.post(
'/save',
authMiddleware,
async (req: Request, res: Response, next: NextFunction) => {
const user = req.user;

if (!isAuthenticated(user)) {
return next(new UnauthorizedError(ErrorMessages.unauthorized));
}
try {
logger.info(`[${req.traceId}] Body: ${JSON.stringify(req.body)}`);

const validationResult = CreateNotificcationSettingsSchema.safeParse(
req.body,
);

if (!validationResult.success) {
throw new UnprocessableEntityError(
validationResult.error.errors[0].message,
);
}

const userNotificationSettings = req.body;

await UserNotificationSettingsCrud.saveNotificationSettings({
userId: user.id,
dailyReminder: userNotificationSettings.dailyReminder,
});

return res.status(200).json({ success: true });
} catch (error: unknown) {
logger.error(`Error saving user notification settings: ${error}`);
return res.status(404).json({ isSuccess: false, error });
}
},
);

export default route;
29 changes: 29 additions & 0 deletions src/api/userNotificationSettings/notificationSettings.crud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { UserNotificationSettings } from '~/database/models/UserNotificationSettings';

export interface UserNotificationSettings {
id: string;
createdAt: string;
updatedAt: string;
userId: string;
dailyReminder: boolean;
}

type CreateUserNotificationSettingsPayload = Omit<
UserNotificationSettings,
'id' | 'createdAt' | 'updatedAt'
>;

export class UserNotificationSettingsCrud {
static async saveNotificationSettings(
payload: CreateUserNotificationSettingsPayload,
) {
return UserNotificationSettings.upsert({
userId: payload.userId,
dailyReminder: payload.dailyReminder,
});
}

static async getNotificationSettingsByUserId(userId: string) {
return UserNotificationSettings.findOne({ where: { userId } });
}
}
5 changes: 5 additions & 0 deletions src/api/userNotificationSettings/validation.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as zod from 'zod';

export const CreateNotificcationSettingsSchema = zod.object({
dailyReminder: zod.boolean(),
});
1 change: 0 additions & 1 deletion src/core/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './response';
export * from './pagination';
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('userNotificationSettings', {
id: {
type: Sequelize.UUIDV4,
primaryKey: true,
defaultValue: Sequelize.UUIDV4,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
},
dailyReminder: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isIn: [[true, false]],
},
},

userId: {
type: Sequelize.UUIDV4,
allowNull: false,
unique: true,
references: {
model: 'user',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
});
},

async down(queryInterface, Sequelize) {
await queryInterface.dropTable('userNotificationSettings');
},
};
37 changes: 37 additions & 0 deletions src/database/models/UserNotificationSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { DataTypes } from 'sequelize';

import Sequelize from '../connection';
import { User } from './User';

export const UserNotificationSettings = Sequelize.define(
'userNotificationSettings',
{
id: {
type: DataTypes.UUIDV4,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},

dailyReminder: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isIn: [[true, false]],
},
},

userId: {
type: DataTypes.UUIDV4,
allowNull: false,
unique: true,
references: {
model: User,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE',
},
},
{},
);
Loading
Loading