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 logo #36

Merged
merged 5 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 216 additions & 1 deletion src/api/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import express, { NextFunction, Request, Response } from 'express';

import { UserCrud } from './user.crud';
import { UpdateUserLogoSchema } from './validation.schema';

import { ErrorMessages } from '~/core/dictionary/error.messages';
import { UnauthorizedError } from '~/core/errors';
import { UnauthorizedError, UnprocessableEntityError } from '~/core/errors';
import { isAuthenticated } from '~/shared/user';

const route = express.Router();
Expand Down Expand Up @@ -50,6 +53,9 @@ const route = express.Router();
* name:
* type: string
* example: "John Doe"
* logo:
* type: string
* example: "Base64"
* 401:
* description: Unauthorized - User is not authenticated
* content:
Expand Down Expand Up @@ -101,4 +107,213 @@ route.get('/', (req: Request, res: Response, next: NextFunction) => {
});
});

/**
* @swagger
* /api/protected/user/logo:
* post:
* summary: Update or create a user logo for current user
* tags: [User logo]
* security:
* - bearerAuth: [] # Indicates that authentication is required
* requestBody:
* required: true
* description: User logo base64 image
* content:
* application/json:
* schema:
* type: object
* properties:
* logo:
* type: string
* description: Base64 string of user logo
* example: "base64-image"
* responses:
* 200:
* description: User logo updated successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: USER_LOGO_UPDATED
* statusCode:
* type: integer
* example: 200
* message:
* type: string
* example: User logo updated successfully
* isSuccess:
* type: boolean
* example: true
* 422:
* description: Unprocessable Entity - Invalid base64
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: UNPROCESSABLE_ENTITY_ERROR
* statusCode:
* type: integer
* example: 422
* message:
* type: string
* example: Invalid base64
* 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.post('/logo', async (req: Request, res: Response, next: NextFunction) => {
try {
const user = req.user;

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

const parsedBody = UpdateUserLogoSchema.safeParse(req.body);

if (parsedBody.error) {
throw new UnprocessableEntityError(parsedBody.error.errors[0].message);
}

await UserCrud.update({
logo: parsedBody.data.logo,
userId: user.id,
});

return res.status(201).json({
type: 'USER_LOGO_UPDATED',
statusCode: 201,
message: `User logo updated successfully.`,
isSuccess: true,
details: {},
});
} catch (error: unknown) {
return next(error);
}
});

/**
* @swagger
* /api/protected/user/logo:
* delete:
* summary: Delete a user logo for current user
* tags: [User]
* security:
* - bearerAuth: [] # Indicates that authentication is required
* responses:
* 200:
* description: User logo successfully deleted
* content:
* application/json:
* schema:
* type: object
* properties:
* type:
* type: string
* example: USER_LOGO_DELETED
* statusCode:
* type: integer
* example: 200
* message:
* type: string
* example: User logo deleted successfully
* isSuccess:
* 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"
* 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.delete(
'/logo',
async (req: Request, res: Response, next: NextFunction) => {
const user = req.user;

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

try {
await UserCrud.delete({
userId: user.id,
});

return res.status(200).json({
type: 'USER_LOGO_DELETED',
statusCode: 200,
message: 'User logo deleted successfully',
isSuccess: true,
});
} catch (error: unknown) {
return next(error);
}
},
);

export default route;
34 changes: 34 additions & 0 deletions src/api/user/user.crud.ts
vmkhitaryanscn marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
deleteUserLogoDBPayload,
updateUserLogoDBPayload,
} from './validation.schema';

import { User } from '~/database/models/User';

export class UserCrud {
static update(payload: updateUserLogoDBPayload) {
return User.update(
{
logo: payload.logo,
},
{
where: {
id: payload.userId,
},
},
);
}

static delete(payload: deleteUserLogoDBPayload) {
return User.update(
{
logo: null,
},
{
where: {
id: payload.userId,
},
},
);
}
}
14 changes: 14 additions & 0 deletions src/api/user/validation.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import z from 'zod';

export type updateUserLogoReqPayload = z.infer<typeof UpdateUserLogoSchema>;

export interface deleteUserLogoDBPayload {
userId: string;
}

export type updateUserLogoDBPayload = updateUserLogoReqPayload &
deleteUserLogoDBPayload;

export const UpdateUserLogoSchema = z.object({
logo: z.string().base64(),
});
17 changes: 17 additions & 0 deletions src/database/migrations/20241218150018-add-logo-to-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn('user', 'logo', {
type: Sequelize.TEXT,
allowNull: true,
unique: false,
defaultValue: null,
});
},

async down(queryInterface, Sequelize) {
await queryInterface.removeColumn('user', 'logo');
},
};
4 changes: 4 additions & 0 deletions src/database/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const User = Sequelize.define(
isEmail: true,
},
},
logo: {
type: DataTypes.TEXT,
allowNull: true,
},
},
{},
);
Loading