feat(services): add PUT /guilds/[guildId]/icon
This commit is contained in:
parent
14eac3cadb
commit
56c613b5cf
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,4 +33,3 @@ npm-debug.log*
|
|||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
uploads
|
|
||||||
|
@ -9,12 +9,12 @@ generator client {
|
|||||||
|
|
||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String @unique @db.VarChar(255)
|
name String @unique @db.VarChar(30)
|
||||||
email String? @unique @db.VarChar(255)
|
email String? @unique @db.VarChar(254)
|
||||||
password String? @db.Text
|
password String? @db.Text
|
||||||
logo String? @db.Text
|
logo String? @db.Text
|
||||||
status String? @db.VarChar(255)
|
status String? @db.VarChar(50)
|
||||||
biography String? @db.Text
|
biography String? @db.VarChar(160)
|
||||||
website String? @db.VarChar(255)
|
website String? @db.VarChar(255)
|
||||||
isConfirmed Boolean @default(false)
|
isConfirmed Boolean @default(false)
|
||||||
temporaryToken String?
|
temporaryToken String?
|
||||||
@ -29,8 +29,8 @@ model User {
|
|||||||
|
|
||||||
model UserSetting {
|
model UserSetting {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
language String @default("en") @db.VarChar(255)
|
language String @default("en") @db.VarChar(10)
|
||||||
theme String @default("dark") @db.VarChar(255)
|
theme String @default("dark") @db.VarChar(10)
|
||||||
isPublicEmail Boolean @default(false)
|
isPublicEmail Boolean @default(false)
|
||||||
isPublicGuilds Boolean @default(false)
|
isPublicGuilds Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@ -51,7 +51,7 @@ model RefreshToken {
|
|||||||
model OAuth {
|
model OAuth {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
providerId String @db.Text
|
providerId String @db.Text
|
||||||
provider String @db.VarChar(255)
|
provider String @db.VarChar(20)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
userId Int @unique
|
userId Int @unique
|
||||||
@ -72,7 +72,7 @@ model Member {
|
|||||||
|
|
||||||
model Guild {
|
model Guild {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String @db.VarChar(255)
|
name String @db.VarChar(30)
|
||||||
icon String? @db.Text
|
icon String? @db.Text
|
||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@ -83,7 +83,7 @@ model Guild {
|
|||||||
|
|
||||||
model Channel {
|
model Channel {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String @db.VarChar(255)
|
name String @db.VarChar(20)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
guildId Int @unique
|
guildId Int @unique
|
||||||
@ -94,8 +94,8 @@ model Channel {
|
|||||||
model Message {
|
model Message {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
value String @db.Text
|
value String @db.Text
|
||||||
type String @default("text") @db.VarChar(255)
|
type String @default("text") @db.VarChar(10)
|
||||||
mimetype String @default("text/plain") @db.VarChar(255)
|
mimetype String @default("text/plain") @db.VarChar(127)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
memberId Int @unique
|
memberId Int @unique
|
||||||
|
@ -8,7 +8,7 @@ export const types = [Type.Literal('text')]
|
|||||||
|
|
||||||
export const channelSchema = {
|
export const channelSchema = {
|
||||||
id,
|
id,
|
||||||
name: Type.String({ maxLength: 255 }),
|
name: Type.String({ minLength: 1, maxLength: 20 }),
|
||||||
createdAt: date.createdAt,
|
createdAt: date.createdAt,
|
||||||
updatedAt: date.updatedAt,
|
updatedAt: date.updatedAt,
|
||||||
guildId: id
|
guildId: id
|
||||||
|
@ -5,9 +5,9 @@ import { date, id } from './utils.js'
|
|||||||
|
|
||||||
export const guildSchema = {
|
export const guildSchema = {
|
||||||
id,
|
id,
|
||||||
name: Type.String({ minLength: 3, maxLength: 30 }),
|
name: Type.String({ minLength: 1, maxLength: 30 }),
|
||||||
icon: Type.String({ format: 'uri-reference' }),
|
icon: Type.Union([Type.String({ format: 'uri-reference' }), Type.Null()]),
|
||||||
description: Type.String({ maxLength: 160 }),
|
description: Type.Union([Type.String({ maxLength: 160 }), Type.Null()]),
|
||||||
createdAt: date.createdAt,
|
createdAt: date.createdAt,
|
||||||
updatedAt: date.updatedAt
|
updatedAt: date.updatedAt
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,13 @@ export const types = [Type.Literal('text'), Type.Literal('file')]
|
|||||||
|
|
||||||
export const messageSchema = {
|
export const messageSchema = {
|
||||||
id,
|
id,
|
||||||
value: Type.String(),
|
value: Type.String({
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: 20_000
|
||||||
|
}),
|
||||||
type: Type.Union(types, { default: 'text' }),
|
type: Type.Union(types, { default: 'text' }),
|
||||||
mimetype: Type.String({
|
mimetype: Type.String({
|
||||||
maxLength: 255,
|
maxLength: 127,
|
||||||
default: 'text/plain',
|
default: 'text/plain',
|
||||||
format: 'mimetype'
|
format: 'mimetype'
|
||||||
}),
|
}),
|
||||||
|
@ -19,11 +19,11 @@ export interface UserRequest {
|
|||||||
export const userSchema = {
|
export const userSchema = {
|
||||||
id,
|
id,
|
||||||
name: Type.String({ minLength: 1, maxLength: 30 }),
|
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(),
|
password: Type.String(),
|
||||||
logo: Type.String({ format: 'uri-reference' }),
|
logo: Type.String({ format: 'uri-reference' }),
|
||||||
status: Type.String({ maxLength: 255 }),
|
status: Type.String({ maxLength: 50 }),
|
||||||
biography: Type.String(),
|
biography: Type.String({ maxLength: 160 }),
|
||||||
website: Type.String({ maxLength: 255, format: 'uri-reference' }),
|
website: Type.String({ maxLength: 255, format: 'uri-reference' }),
|
||||||
isConfirmed: Type.Boolean({ default: false }),
|
isConfirmed: Type.Boolean({ default: false }),
|
||||||
temporaryToken: Type.String(),
|
temporaryToken: Type.String(),
|
||||||
@ -32,18 +32,22 @@ export const userSchema = {
|
|||||||
updatedAt: date.updatedAt
|
updatedAt: date.updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userPublicSchema = {
|
export const userPublicWithoutSettingsSchema = {
|
||||||
id,
|
id,
|
||||||
name: userSchema.name,
|
name: userSchema.name,
|
||||||
email: Type.Optional(userSchema.email),
|
email: Type.Union([userSchema.email, Type.Null()]),
|
||||||
logo: Type.Optional(userSchema.logo),
|
logo: Type.Union([userSchema.logo, Type.Null()]),
|
||||||
status: Type.Optional(userSchema.status),
|
status: Type.Union([userSchema.status, Type.Null()]),
|
||||||
biography: Type.Optional(userSchema.biography),
|
biography: Type.Union([userSchema.biography, Type.Null()]),
|
||||||
website: Type.Optional(userSchema.website),
|
website: Type.Union([userSchema.website, Type.Null()]),
|
||||||
isConfirmed: userSchema.isConfirmed,
|
isConfirmed: userSchema.isConfirmed,
|
||||||
createdAt: date.createdAt,
|
createdAt: date.createdAt,
|
||||||
updatedAt: date.updatedAt,
|
updatedAt: date.updatedAt
|
||||||
settings: Type.Optional(Type.Object(userSettingsSchema))
|
}
|
||||||
|
|
||||||
|
export const userPublicSchema = {
|
||||||
|
...userPublicWithoutSettingsSchema,
|
||||||
|
settings: Type.Object(userSettingsSchema)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userCurrentSchema = Type.Object({
|
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 { FastifyPluginAsync } from 'fastify'
|
||||||
|
|
||||||
import { postGuilds } from './post.js'
|
import { postGuilds } from './post.js'
|
||||||
|
import { putGuildIconById } from './[guildId]/icon/put.js'
|
||||||
|
|
||||||
export const guildsService: FastifyPluginAsync = async (fastify) => {
|
export const guildsService: FastifyPluginAsync = async (fastify) => {
|
||||||
await fastify.register(postGuilds)
|
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 { guildSchema } from '../../models/Guild.js'
|
||||||
import { channelSchema } from '../../models/Channel.js'
|
import { channelSchema } from '../../models/Channel.js'
|
||||||
import { memberSchema } from '../../models/Member.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({
|
const bodyPostServiceSchema = Type.Object({
|
||||||
name: guildSchema.name,
|
name: guildSchema.name,
|
||||||
@ -33,7 +34,7 @@ const postServiceSchema: FastifySchema = {
|
|||||||
members: Type.Array(
|
members: Type.Array(
|
||||||
Type.Object({
|
Type.Object({
|
||||||
...memberSchema,
|
...memberSchema,
|
||||||
user: Type.Object(userPublicSchema)
|
user: Type.Object(userPublicWithoutSettingsSchema)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -59,7 +60,9 @@ export const postGuilds: FastifyPluginAsync = async (fastify) => {
|
|||||||
throw fastify.httpErrors.forbidden()
|
throw fastify.httpErrors.forbidden()
|
||||||
}
|
}
|
||||||
const { name, description } = request.body
|
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({
|
const channel = await prisma.channel.create({
|
||||||
data: { name: 'general', guildId: guild.id }
|
data: { name: 'general', guildId: guild.id }
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,7 @@ const parametersGetUserSchema = Type.Object({
|
|||||||
userId: userPublicSchema.id
|
userId: userPublicSchema.id
|
||||||
})
|
})
|
||||||
|
|
||||||
export type ParametersGetUser = Static<typeof parametersGetUserSchema>
|
type ParametersGetUser = Static<typeof parametersGetUserSchema>
|
||||||
|
|
||||||
const getServiceSchema: FastifySchema = {
|
const getServiceSchema: FastifySchema = {
|
||||||
description: 'GET the public user informations with its id',
|
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 { Type } from '@sinclair/typebox'
|
||||||
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
import { FastifyPluginAsync, FastifySchema } from 'fastify'
|
||||||
|
|
||||||
import authenticateUser from '../../../../tools/plugins/authenticateUser.js'
|
import authenticateUser from '../../../../tools/plugins/authenticateUser.js'
|
||||||
import { fastifyErrors } from '../../../../models/utils.js'
|
import { fastifyErrors } from '../../../../models/utils.js'
|
||||||
import fastifyMultipart, { Multipart } from 'fastify-multipart'
|
import fastifyMultipart from 'fastify-multipart'
|
||||||
import {
|
|
||||||
maximumImageSize,
|
|
||||||
supportedImageMimetype,
|
|
||||||
ROOT_URL
|
|
||||||
} from '../../../../tools/configurations'
|
|
||||||
import prisma from '../../../../tools/database/prisma.js'
|
import prisma from '../../../../tools/database/prisma.js'
|
||||||
|
import { uploadImage } from '../../../../tools/utils/uploadImage.js'
|
||||||
|
|
||||||
const putServiceSchema: FastifySchema = {
|
const putServiceSchema: FastifySchema = {
|
||||||
description: 'Edit the current connected user logo',
|
description: 'Edit the current connected user logo',
|
||||||
@ -52,36 +44,11 @@ export const putCurrentUserLogo: FastifyPluginAsync = async (fastify) => {
|
|||||||
if (request.user == null) {
|
if (request.user == null) {
|
||||||
throw fastify.httpErrors.forbidden()
|
throw fastify.httpErrors.forbidden()
|
||||||
}
|
}
|
||||||
let files: Multipart[] = []
|
const logo = await uploadImage({
|
||||||
try {
|
fastify,
|
||||||
files = await request.saveRequestFiles({
|
request,
|
||||||
limits: {
|
folderInUploadsFolder: 'users'
|
||||||
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)
|
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { id: request.user.current.id },
|
where: { id: request.user.current.id },
|
||||||
data: { logo }
|
data: { logo }
|
||||||
|
@ -10,6 +10,7 @@ import { userCurrentSchema, userSchema } from '../../../models/User.js'
|
|||||||
import { sendEmail } from '../../../tools/email/sendEmail.js'
|
import { sendEmail } from '../../../tools/email/sendEmail.js'
|
||||||
import { HOST, PORT } from '../../../tools/configurations/index.js'
|
import { HOST, PORT } from '../../../tools/configurations/index.js'
|
||||||
import { Language, Theme } from '../../../models/UserSettings.js'
|
import { Language, Theme } from '../../../models/UserSettings.js'
|
||||||
|
import { parseStringNullish } from '../../../tools/utils/parseStringNullish.js'
|
||||||
|
|
||||||
const bodyPutServiceSchema = Type.Object({
|
const bodyPutServiceSchema = Type.Object({
|
||||||
name: Type.Optional(userSchema.name),
|
name: Type.Optional(userSchema.name),
|
||||||
@ -117,9 +118,12 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => {
|
|||||||
where: { id: request.user.current.id },
|
where: { id: request.user.current.id },
|
||||||
data: {
|
data: {
|
||||||
name: name ?? request.user.current.name,
|
name: name ?? request.user.current.name,
|
||||||
status: status ?? request.user.current.status,
|
status: parseStringNullish(request.user.current.status, status),
|
||||||
biography: biography ?? request.user.current.biography,
|
biography: parseStringNullish(
|
||||||
website: website ?? request.user.current.website
|
request.user.current.biography,
|
||||||
|
biography
|
||||||
|
),
|
||||||
|
website: parseStringNullish(request.user.current.website, website)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
reply.statusCode = 200
|
reply.statusCode = 200
|
||||||
|
@ -5,7 +5,7 @@ import { OAuthStrategy } from '../OAuthStrategy.js'
|
|||||||
|
|
||||||
const oauthStrategy = new OAuthStrategy('discord')
|
const oauthStrategy = new OAuthStrategy('discord')
|
||||||
|
|
||||||
describe('/utils/OAuthStrategy - callbackSignin', () => {
|
describe('/tools/utils/OAuthStrategy - callbackSignin', () => {
|
||||||
it('should signup the user', async () => {
|
it('should signup the user', async () => {
|
||||||
const name = 'Martin'
|
const name = 'Martin'
|
||||||
const id = '12345'
|
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 () => {
|
it('should add the strategy to the user', async () => {
|
||||||
const name = userExample.name
|
const name = userExample.name
|
||||||
const id = '12345'
|
const id = '12345'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { buildQueryURL } from '../buildQueryURL.js'
|
import { buildQueryURL } from '../buildQueryURL.js'
|
||||||
|
|
||||||
test('controllers/users/utils/buildQueryUrl', () => {
|
test('/tools/utils/buildQueryUrl', () => {
|
||||||
expect(
|
expect(
|
||||||
buildQueryURL('http://localhost:8080', {
|
buildQueryURL('http://localhost:8080', {
|
||||||
test: 'query'
|
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