feat(services): add PUT /guilds/[guildId]/icon
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -33,4 +33,3 @@ npm-debug.log* | ||||
|  | ||||
| # misc | ||||
| .DS_Store | ||||
| uploads | ||||
|   | ||||
| @@ -9,12 +9,12 @@ generator client { | ||||
|  | ||||
| model User { | ||||
|   id                       Int            @id @default(autoincrement()) | ||||
|   name                     String         @unique @db.VarChar(255) | ||||
|   email                    String?        @unique @db.VarChar(255) | ||||
|   name                     String         @unique @db.VarChar(30) | ||||
|   email                    String?        @unique @db.VarChar(254) | ||||
|   password                 String?        @db.Text | ||||
|   logo                     String?        @db.Text | ||||
|   status                   String?        @db.VarChar(255) | ||||
|   biography                String?        @db.Text | ||||
|   status                   String?        @db.VarChar(50) | ||||
|   biography                String?        @db.VarChar(160) | ||||
|   website                  String?        @db.VarChar(255) | ||||
|   isConfirmed              Boolean        @default(false) | ||||
|   temporaryToken           String? | ||||
| @@ -29,8 +29,8 @@ model User { | ||||
|  | ||||
| model UserSetting { | ||||
|   id             Int      @id @default(autoincrement()) | ||||
|   language       String   @default("en") @db.VarChar(255) | ||||
|   theme          String   @default("dark") @db.VarChar(255) | ||||
|   language       String   @default("en") @db.VarChar(10) | ||||
|   theme          String   @default("dark") @db.VarChar(10) | ||||
|   isPublicEmail  Boolean  @default(false) | ||||
|   isPublicGuilds Boolean  @default(false) | ||||
|   createdAt      DateTime @default(now()) | ||||
| @@ -51,7 +51,7 @@ model RefreshToken { | ||||
| model OAuth { | ||||
|   id         Int      @id @default(autoincrement()) | ||||
|   providerId String   @db.Text | ||||
|   provider   String   @db.VarChar(255) | ||||
|   provider   String   @db.VarChar(20) | ||||
|   createdAt  DateTime @default(now()) | ||||
|   updatedAt  DateTime @default(now()) @updatedAt | ||||
|   userId     Int      @unique | ||||
| @@ -72,7 +72,7 @@ model Member { | ||||
|  | ||||
| model Guild { | ||||
|   id          Int       @id @default(autoincrement()) | ||||
|   name        String    @db.VarChar(255) | ||||
|   name        String    @db.VarChar(30) | ||||
|   icon        String?   @db.Text | ||||
|   description String?   @db.Text | ||||
|   createdAt   DateTime  @default(now()) | ||||
| @@ -83,7 +83,7 @@ model Guild { | ||||
|  | ||||
| model Channel { | ||||
|   id        Int       @id @default(autoincrement()) | ||||
|   name      String    @db.VarChar(255) | ||||
|   name      String    @db.VarChar(20) | ||||
|   createdAt DateTime  @default(now()) | ||||
|   updatedAt DateTime  @default(now()) @updatedAt | ||||
|   guildId   Int       @unique | ||||
| @@ -94,8 +94,8 @@ model Channel { | ||||
| model Message { | ||||
|   id        Int      @id @default(autoincrement()) | ||||
|   value     String   @db.Text | ||||
|   type      String   @default("text") @db.VarChar(255) | ||||
|   mimetype  String   @default("text/plain") @db.VarChar(255) | ||||
|   type      String   @default("text") @db.VarChar(10) | ||||
|   mimetype  String   @default("text/plain") @db.VarChar(127) | ||||
|   createdAt DateTime @default(now()) | ||||
|   updatedAt DateTime @default(now()) @updatedAt | ||||
|   memberId  Int      @unique | ||||
|   | ||||
| @@ -8,7 +8,7 @@ export const types = [Type.Literal('text')] | ||||
|  | ||||
| export const channelSchema = { | ||||
|   id, | ||||
|   name: Type.String({ maxLength: 255 }), | ||||
|   name: Type.String({ minLength: 1, maxLength: 20 }), | ||||
|   createdAt: date.createdAt, | ||||
|   updatedAt: date.updatedAt, | ||||
|   guildId: id | ||||
|   | ||||
| @@ -5,9 +5,9 @@ import { date, id } from './utils.js' | ||||
|  | ||||
| export const guildSchema = { | ||||
|   id, | ||||
|   name: Type.String({ minLength: 3, maxLength: 30 }), | ||||
|   icon: Type.String({ format: 'uri-reference' }), | ||||
|   description: Type.String({ maxLength: 160 }), | ||||
|   name: Type.String({ minLength: 1, maxLength: 30 }), | ||||
|   icon: Type.Union([Type.String({ format: 'uri-reference' }), Type.Null()]), | ||||
|   description: Type.Union([Type.String({ maxLength: 160 }), Type.Null()]), | ||||
|   createdAt: date.createdAt, | ||||
|   updatedAt: date.updatedAt | ||||
| } | ||||
|   | ||||
| @@ -6,10 +6,13 @@ export const types = [Type.Literal('text'), Type.Literal('file')] | ||||
|  | ||||
| export const messageSchema = { | ||||
|   id, | ||||
|   value: Type.String(), | ||||
|   value: Type.String({ | ||||
|     minLength: 1, | ||||
|     maxLength: 20_000 | ||||
|   }), | ||||
|   type: Type.Union(types, { default: 'text' }), | ||||
|   mimetype: Type.String({ | ||||
|     maxLength: 255, | ||||
|     maxLength: 127, | ||||
|     default: 'text/plain', | ||||
|     format: 'mimetype' | ||||
|   }), | ||||
|   | ||||
| @@ -19,11 +19,11 @@ export interface UserRequest { | ||||
| export const userSchema = { | ||||
|   id, | ||||
|   name: Type.String({ minLength: 1, maxLength: 30 }), | ||||
|   email: Type.String({ minLength: 1, maxLength: 255, format: 'email' }), | ||||
|   email: Type.String({ minLength: 1, maxLength: 254, format: 'email' }), | ||||
|   password: Type.String(), | ||||
|   logo: Type.String({ format: 'uri-reference' }), | ||||
|   status: Type.String({ maxLength: 255 }), | ||||
|   biography: Type.String(), | ||||
|   status: Type.String({ maxLength: 50 }), | ||||
|   biography: Type.String({ maxLength: 160 }), | ||||
|   website: Type.String({ maxLength: 255, format: 'uri-reference' }), | ||||
|   isConfirmed: Type.Boolean({ default: false }), | ||||
|   temporaryToken: Type.String(), | ||||
| @@ -32,18 +32,22 @@ export const userSchema = { | ||||
|   updatedAt: date.updatedAt | ||||
| } | ||||
|  | ||||
| export const userPublicSchema = { | ||||
| export const userPublicWithoutSettingsSchema = { | ||||
|   id, | ||||
|   name: userSchema.name, | ||||
|   email: Type.Optional(userSchema.email), | ||||
|   logo: Type.Optional(userSchema.logo), | ||||
|   status: Type.Optional(userSchema.status), | ||||
|   biography: Type.Optional(userSchema.biography), | ||||
|   website: Type.Optional(userSchema.website), | ||||
|   email: Type.Union([userSchema.email, Type.Null()]), | ||||
|   logo: Type.Union([userSchema.logo, Type.Null()]), | ||||
|   status: Type.Union([userSchema.status, Type.Null()]), | ||||
|   biography: Type.Union([userSchema.biography, Type.Null()]), | ||||
|   website: Type.Union([userSchema.website, Type.Null()]), | ||||
|   isConfirmed: userSchema.isConfirmed, | ||||
|   createdAt: date.createdAt, | ||||
|   updatedAt: date.updatedAt, | ||||
|   settings: Type.Optional(Type.Object(userSettingsSchema)) | ||||
|   updatedAt: date.updatedAt | ||||
| } | ||||
|  | ||||
| export const userPublicSchema = { | ||||
|   ...userPublicWithoutSettingsSchema, | ||||
|   settings: Type.Object(userSettingsSchema) | ||||
| } | ||||
|  | ||||
| export const userCurrentSchema = Type.Object({ | ||||
|   | ||||
							
								
								
									
										80
									
								
								src/services/guilds/[guildId]/icon/put.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/services/guilds/[guildId]/icon/put.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| import { Static, Type } from '@sinclair/typebox' | ||||
| import { FastifyPluginAsync, FastifySchema } from 'fastify' | ||||
|  | ||||
| import authenticateUser from '../../../../tools/plugins/authenticateUser.js' | ||||
| import { fastifyErrors } from '../../../../models/utils.js' | ||||
| import fastifyMultipart from 'fastify-multipart' | ||||
| import prisma from '../../../../tools/database/prisma.js' | ||||
| import { uploadImage } from '../../../../tools/utils/uploadImage.js' | ||||
| import { guildSchema } from '../../../../models/Guild.js' | ||||
|  | ||||
| const parametersSchema = Type.Object({ | ||||
|   guildId: guildSchema.id | ||||
| }) | ||||
|  | ||||
| type Parameters = Static<typeof parametersSchema> | ||||
|  | ||||
| const putServiceSchema: FastifySchema = { | ||||
|   description: 'Edit the icon of the guild with its id', | ||||
|   tags: ['guilds'] as string[], | ||||
|   consumes: ['multipart/form-data'] as string[], | ||||
|   produces: ['application/json'] as string[], | ||||
|   security: [ | ||||
|     { | ||||
|       bearerAuth: [] | ||||
|     } | ||||
|   ] as Array<{ [key: string]: [] }>, | ||||
|   params: parametersSchema, | ||||
|   response: { | ||||
|     200: Type.Object({ | ||||
|       guild: Type.Object({ | ||||
|         icon: Type.String() | ||||
|       }) | ||||
|     }), | ||||
|     400: fastifyErrors[400], | ||||
|     401: fastifyErrors[401], | ||||
|     403: fastifyErrors[403], | ||||
|     404: fastifyErrors[404], | ||||
|     431: fastifyErrors[431], | ||||
|     500: fastifyErrors[500] | ||||
|   } | ||||
| } as const | ||||
|  | ||||
| export const putGuildIconById: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(authenticateUser) | ||||
|  | ||||
|   await fastify.register(fastifyMultipart) | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'PUT', | ||||
|     url: '/guilds/:guildId/icon', | ||||
|     schema: putServiceSchema, | ||||
|     handler: async (request, reply) => { | ||||
|       if (request.user == null) { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { guildId } = request.params | ||||
|       const guild = await prisma.guild.findUnique({ where: { id: guildId } }) | ||||
|       if (guild == null) { | ||||
|         throw fastify.httpErrors.notFound() | ||||
|       } | ||||
|       const icon = await uploadImage({ | ||||
|         fastify, | ||||
|         request, | ||||
|         folderInUploadsFolder: 'guilds' | ||||
|       }) | ||||
|       await prisma.guild.update({ | ||||
|         where: { id: guildId }, | ||||
|         data: { icon } | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|       return { | ||||
|         guild: { | ||||
|           icon | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -1,7 +1,9 @@ | ||||
| import { FastifyPluginAsync } from 'fastify' | ||||
|  | ||||
| import { postGuilds } from './post.js' | ||||
| import { putGuildIconById } from './[guildId]/icon/put.js' | ||||
|  | ||||
| export const guildsService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(postGuilds) | ||||
|   await fastify.register(putGuildIconById) | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,8 @@ import authenticateUser from '../../tools/plugins/authenticateUser.js' | ||||
| import { guildSchema } from '../../models/Guild.js' | ||||
| import { channelSchema } from '../../models/Channel.js' | ||||
| import { memberSchema } from '../../models/Member.js' | ||||
| import { userPublicSchema } from '../../models/User.js' | ||||
| import { userPublicWithoutSettingsSchema } from '../../models/User.js' | ||||
| import { parseStringNullish } from '../../tools/utils/parseStringNullish.js' | ||||
|  | ||||
| const bodyPostServiceSchema = Type.Object({ | ||||
|   name: guildSchema.name, | ||||
| @@ -33,7 +34,7 @@ const postServiceSchema: FastifySchema = { | ||||
|         members: Type.Array( | ||||
|           Type.Object({ | ||||
|             ...memberSchema, | ||||
|             user: Type.Object(userPublicSchema) | ||||
|             user: Type.Object(userPublicWithoutSettingsSchema) | ||||
|           }) | ||||
|         ) | ||||
|       }) | ||||
| @@ -59,7 +60,9 @@ export const postGuilds: FastifyPluginAsync = async (fastify) => { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { name, description } = request.body | ||||
|       const guild = await prisma.guild.create({ data: { name, description } }) | ||||
|       const guild = await prisma.guild.create({ | ||||
|         data: { name, description: parseStringNullish(description) } | ||||
|       }) | ||||
|       const channel = await prisma.channel.create({ | ||||
|         data: { name: 'general', guildId: guild.id } | ||||
|       }) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ const parametersGetUserSchema = Type.Object({ | ||||
|   userId: userPublicSchema.id | ||||
| }) | ||||
|  | ||||
| export type ParametersGetUser = Static<typeof parametersGetUserSchema> | ||||
| type ParametersGetUser = Static<typeof parametersGetUserSchema> | ||||
|  | ||||
| const getServiceSchema: FastifySchema = { | ||||
|   description: 'GET the public user informations with its id', | ||||
|   | ||||
| @@ -1,19 +1,11 @@ | ||||
| import fs from 'node:fs' | ||||
| import { URL } from 'node:url' | ||||
| import { randomUUID } from 'node:crypto' | ||||
|  | ||||
| import { Type } from '@sinclair/typebox' | ||||
| import { FastifyPluginAsync, FastifySchema } from 'fastify' | ||||
|  | ||||
| import authenticateUser from '../../../../tools/plugins/authenticateUser.js' | ||||
| import { fastifyErrors } from '../../../../models/utils.js' | ||||
| import fastifyMultipart, { Multipart } from 'fastify-multipart' | ||||
| import { | ||||
|   maximumImageSize, | ||||
|   supportedImageMimetype, | ||||
|   ROOT_URL | ||||
| } from '../../../../tools/configurations' | ||||
| import fastifyMultipart from 'fastify-multipart' | ||||
| import prisma from '../../../../tools/database/prisma.js' | ||||
| import { uploadImage } from '../../../../tools/utils/uploadImage.js' | ||||
|  | ||||
| const putServiceSchema: FastifySchema = { | ||||
|   description: 'Edit the current connected user logo', | ||||
| @@ -52,36 +44,11 @@ export const putCurrentUserLogo: FastifyPluginAsync = async (fastify) => { | ||||
|       if (request.user == null) { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       let files: Multipart[] = [] | ||||
|       try { | ||||
|         files = await request.saveRequestFiles({ | ||||
|           limits: { | ||||
|             files: 1, | ||||
|             fileSize: maximumImageSize * 1024 * 1024 | ||||
|           } | ||||
|         }) | ||||
|       } catch (error) { | ||||
|         throw fastify.httpErrors.requestHeaderFieldsTooLarge( | ||||
|           `body.logo should be less than ${maximumImageSize}mb.` | ||||
|         ) | ||||
|       } | ||||
|       if (files.length !== 1) { | ||||
|         throw fastify.httpErrors.badRequest('You must upload at most one file.') | ||||
|       } | ||||
|       const image = files[0] | ||||
|       if (!supportedImageMimetype.includes(image.mimetype)) { | ||||
|         throw fastify.httpErrors.badRequest( | ||||
|           `The file must have a valid type (${supportedImageMimetype.join( | ||||
|             ', ' | ||||
|           )}).` | ||||
|         ) | ||||
|       } | ||||
|       const splitedMimetype = image.mimetype.split('/') | ||||
|       const imageExtension = splitedMimetype[1] | ||||
|       const logoPath = `uploads/users/${randomUUID()}.${imageExtension}` | ||||
|       const logoURL = new URL(logoPath, ROOT_URL) | ||||
|       const logo = `/${logoPath}` | ||||
|       await fs.promises.copyFile(image.filepath, logoURL) | ||||
|       const logo = await uploadImage({ | ||||
|         fastify, | ||||
|         request, | ||||
|         folderInUploadsFolder: 'users' | ||||
|       }) | ||||
|       await prisma.user.update({ | ||||
|         where: { id: request.user.current.id }, | ||||
|         data: { logo } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { userCurrentSchema, userSchema } from '../../../models/User.js' | ||||
| import { sendEmail } from '../../../tools/email/sendEmail.js' | ||||
| import { HOST, PORT } from '../../../tools/configurations/index.js' | ||||
| import { Language, Theme } from '../../../models/UserSettings.js' | ||||
| import { parseStringNullish } from '../../../tools/utils/parseStringNullish.js' | ||||
|  | ||||
| const bodyPutServiceSchema = Type.Object({ | ||||
|   name: Type.Optional(userSchema.name), | ||||
| @@ -117,9 +118,12 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => { | ||||
|         where: { id: request.user.current.id }, | ||||
|         data: { | ||||
|           name: name ?? request.user.current.name, | ||||
|           status: status ?? request.user.current.status, | ||||
|           biography: biography ?? request.user.current.biography, | ||||
|           website: website ?? request.user.current.website | ||||
|           status: parseStringNullish(request.user.current.status, status), | ||||
|           biography: parseStringNullish( | ||||
|             request.user.current.biography, | ||||
|             biography | ||||
|           ), | ||||
|           website: parseStringNullish(request.user.current.website, website) | ||||
|         } | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { OAuthStrategy } from '../OAuthStrategy.js' | ||||
|  | ||||
| const oauthStrategy = new OAuthStrategy('discord') | ||||
|  | ||||
| describe('/utils/OAuthStrategy - callbackSignin', () => { | ||||
| describe('/tools/utils/OAuthStrategy - callbackSignin', () => { | ||||
|   it('should signup the user', async () => { | ||||
|     const name = 'Martin' | ||||
|     const id = '12345' | ||||
| @@ -52,7 +52,7 @@ describe('/utils/OAuthStrategy - callbackSignin', () => { | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| describe('/utils/OAuthStrategy - callbackAddStrategy', () => { | ||||
| describe('/tools/utils/OAuthStrategy - callbackAddStrategy', () => { | ||||
|   it('should add the strategy to the user', async () => { | ||||
|     const name = userExample.name | ||||
|     const id = '12345' | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { buildQueryURL } from '../buildQueryURL.js' | ||||
|  | ||||
| test('controllers/users/utils/buildQueryUrl', () => { | ||||
| test('/tools/utils/buildQueryUrl', () => { | ||||
|   expect( | ||||
|     buildQueryURL('http://localhost:8080', { | ||||
|       test: 'query' | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/tools/utils/__test__/parseStringNullish.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/tools/utils/__test__/parseStringNullish.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import { parseStringNullish } from '../parseStringNullish' | ||||
|  | ||||
| const defaultString = 'defaultString' | ||||
|  | ||||
| describe('/tools/utils/parseStringNullish', () => { | ||||
|   it('returns `null` if `string.length === 0`', () => { | ||||
|     expect(parseStringNullish(defaultString, '')).toEqual(null) | ||||
|   }) | ||||
|  | ||||
|   it('returns `defaultString` if `string == null`', () => { | ||||
|     expect(parseStringNullish(defaultString)).toEqual(defaultString) | ||||
|   }) | ||||
|  | ||||
|   it('returns `string` if `string.length > 0`', () => { | ||||
|     expect(parseStringNullish(defaultString, 'string')).toEqual('string') | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										21
									
								
								src/tools/utils/parseStringNullish.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/tools/utils/parseStringNullish.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| /** | ||||
|  * Parse a nullish string: | ||||
|  *  - if `string.length === 0`, it returns `null` | ||||
|  *  - if `string == null`, it returns `defaultString` | ||||
|  *  - if `string.length > 0`, it returns `string` | ||||
|  * @param defaultString | ||||
|  * @param string | ||||
|  * @returns | ||||
|  */ | ||||
| export const parseStringNullish = ( | ||||
|   defaultString: string | null, | ||||
|   string?: string | ||||
| ): string | null => { | ||||
|   if (string != null) { | ||||
|     if (string.length > 0) { | ||||
|       return string | ||||
|     } | ||||
|     return null | ||||
|   } | ||||
|   return defaultString | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/tools/utils/uploadImage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/tools/utils/uploadImage.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| import fs from 'node:fs' | ||||
| import { URL } from 'node:url' | ||||
| import { randomUUID } from 'node:crypto' | ||||
|  | ||||
| import { FastifyInstance, FastifyRequest } from 'fastify' | ||||
|  | ||||
| import { Multipart } from 'fastify-multipart' | ||||
|  | ||||
| import { | ||||
|   maximumImageSize, | ||||
|   supportedImageMimetype, | ||||
|   ROOT_URL | ||||
| } from '../configurations' | ||||
|  | ||||
| export interface UploadImageOptions { | ||||
|   folderInUploadsFolder: 'guilds' | 'messages' | 'users' | ||||
|   request: FastifyRequest | ||||
|   fastify: FastifyInstance | ||||
| } | ||||
|  | ||||
| export const uploadImage = async ( | ||||
|   options: UploadImageOptions | ||||
| ): Promise<string> => { | ||||
|   const { fastify, request, folderInUploadsFolder } = options | ||||
|   let files: Multipart[] = [] | ||||
|   try { | ||||
|     files = await request.saveRequestFiles({ | ||||
|       limits: { | ||||
|         files: 1, | ||||
|         fileSize: maximumImageSize * 1024 * 1024 | ||||
|       } | ||||
|     }) | ||||
|   } catch (error) { | ||||
|     throw fastify.httpErrors.requestHeaderFieldsTooLarge( | ||||
|       `Image should be less than ${maximumImageSize}mb.` | ||||
|     ) | ||||
|   } | ||||
|   if (files.length !== 1) { | ||||
|     throw fastify.httpErrors.badRequest('You must upload at most one file.') | ||||
|   } | ||||
|   const image = files[0] | ||||
|   if (!supportedImageMimetype.includes(image.mimetype)) { | ||||
|     throw fastify.httpErrors.badRequest( | ||||
|       `The file must have a valid type (${supportedImageMimetype.join(', ')}).` | ||||
|     ) | ||||
|   } | ||||
|   const splitedMimetype = image.mimetype.split('/') | ||||
|   const imageExtension = splitedMimetype[1] | ||||
|   const imagePath = `uploads/${folderInUploadsFolder}/${randomUUID()}.${imageExtension}` | ||||
|   const imageURL = new URL(imagePath, ROOT_URL) | ||||
|   const imagePathToStoreInDatabase = `/${imagePath}` | ||||
|   await fs.promises.copyFile(image.filepath, imageURL) | ||||
|   return imagePathToStoreInDatabase | ||||
| } | ||||
							
								
								
									
										0
									
								
								uploads/guilds/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								uploads/guilds/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 2.4 KiB | 
							
								
								
									
										0
									
								
								uploads/users/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								uploads/users/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 9.1 KiB | 
		Reference in New Issue
	
	Block a user