feat: migrate from express to fastify
This commit is contained in:
95
src/tools/utils/OAuthStrategy.ts
Normal file
95
src/tools/utils/OAuthStrategy.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import {
|
||||
expiresIn,
|
||||
generateAccessToken,
|
||||
generateRefreshToken,
|
||||
ResponseJWT
|
||||
} from './jwtToken.js'
|
||||
import prisma from '../database/prisma.js'
|
||||
import { ProviderOAuth } from '../../models/OAuth.js'
|
||||
import { UserRequest } from '../../models/User.js'
|
||||
|
||||
interface ProviderData {
|
||||
name: string
|
||||
id: number | string
|
||||
}
|
||||
|
||||
type ResponseCallbackAddStrategy =
|
||||
| 'success'
|
||||
| 'This account is already used by someone else'
|
||||
| 'You are already using this account'
|
||||
|
||||
export class OAuthStrategy {
|
||||
constructor(public provider: ProviderOAuth) {}
|
||||
|
||||
async callbackAddStrategy(
|
||||
providerData: ProviderData,
|
||||
userRequest: UserRequest
|
||||
): Promise<ResponseCallbackAddStrategy> {
|
||||
const OAuthUser = await prisma.oAuth.findFirst({
|
||||
where: { providerId: providerData.id.toString(), provider: this.provider }
|
||||
})
|
||||
let message: ResponseCallbackAddStrategy = 'success'
|
||||
if (OAuthUser == null) {
|
||||
await prisma.oAuth.create({
|
||||
data: {
|
||||
provider: this.provider,
|
||||
providerId: providerData.id.toString(),
|
||||
userId: userRequest.current.id
|
||||
}
|
||||
})
|
||||
} else if (OAuthUser.userId !== userRequest.current.id) {
|
||||
message = 'This account is already used by someone else'
|
||||
} else {
|
||||
message = 'You are already using this account'
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
async callbackSignin(providerData: ProviderData): Promise<ResponseJWT> {
|
||||
const OAuthUser = await prisma.oAuth.findFirst({
|
||||
where: { providerId: providerData.id.toString(), provider: this.provider }
|
||||
})
|
||||
let userId: number = OAuthUser?.userId ?? 0
|
||||
if (OAuthUser == null) {
|
||||
let name = providerData.name
|
||||
let isAlreadyUsedName = true
|
||||
let countId: string | number = providerData.id
|
||||
while (isAlreadyUsedName) {
|
||||
const foundUsers = await prisma.user.count({ where: { name } })
|
||||
isAlreadyUsedName = foundUsers > 0
|
||||
if (isAlreadyUsedName) {
|
||||
name = `${name}-${countId}`
|
||||
countId = Math.random() * Date.now()
|
||||
}
|
||||
}
|
||||
const user = await prisma.user.create({ data: { name } })
|
||||
await prisma.userSetting.create({
|
||||
data: {
|
||||
userId: user.id
|
||||
}
|
||||
})
|
||||
userId = user.id
|
||||
await prisma.oAuth.create({
|
||||
data: {
|
||||
provider: this.provider,
|
||||
providerId: providerData.id.toString(),
|
||||
userId
|
||||
}
|
||||
})
|
||||
}
|
||||
const accessToken = generateAccessToken({
|
||||
currentStrategy: this.provider,
|
||||
id: userId
|
||||
})
|
||||
const refreshToken = await generateRefreshToken({
|
||||
currentStrategy: this.provider,
|
||||
id: userId
|
||||
})
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn,
|
||||
type: 'Bearer'
|
||||
}
|
||||
}
|
||||
}
|
137
src/tools/utils/__test__/OAuthStrategy.test.ts
Normal file
137
src/tools/utils/__test__/OAuthStrategy.test.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import { userExample } from '../../../models/User.js'
|
||||
import { userSettingsExample } from '../../../models/UserSettings.js'
|
||||
import { prismaMock } from '../../../__test__/setup.js'
|
||||
import { OAuthStrategy } from '../OAuthStrategy.js'
|
||||
|
||||
const oauthStrategy = new OAuthStrategy('discord')
|
||||
|
||||
describe('/utils/OAuthStrategy - callbackSignin', () => {
|
||||
it('should signup the user', async () => {
|
||||
const name = 'Martin'
|
||||
const id = '12345'
|
||||
prismaMock.oAuth.findFirst.mockResolvedValue(null)
|
||||
prismaMock.user.count.mockResolvedValue(0)
|
||||
prismaMock.user.create.mockResolvedValue({
|
||||
...userExample,
|
||||
name
|
||||
})
|
||||
prismaMock.userSetting.create.mockResolvedValue(userSettingsExample)
|
||||
prismaMock.oAuth.create.mockResolvedValue({
|
||||
id: 1,
|
||||
userId: userExample.id,
|
||||
provider: 'discord',
|
||||
providerId: id,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date()
|
||||
})
|
||||
await oauthStrategy.callbackSignin({ id, name })
|
||||
expect(prismaMock.oAuth.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
provider: 'discord',
|
||||
providerId: id
|
||||
}
|
||||
})
|
||||
expect(prismaMock.user.count).toHaveBeenCalledWith({
|
||||
where: { name }
|
||||
})
|
||||
expect(prismaMock.user.create).toHaveBeenCalledWith({
|
||||
data: { name }
|
||||
})
|
||||
expect(prismaMock.userSetting.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
userId: userExample.id
|
||||
}
|
||||
})
|
||||
expect(prismaMock.oAuth.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
userId: userExample.id,
|
||||
provider: 'discord',
|
||||
providerId: id
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('/utils/OAuthStrategy - callbackAddStrategy', () => {
|
||||
it('should add the strategy to the user', async () => {
|
||||
const name = userExample.name
|
||||
const id = '12345'
|
||||
prismaMock.oAuth.findFirst.mockResolvedValue(null)
|
||||
prismaMock.oAuth.create.mockResolvedValue({
|
||||
id: 1,
|
||||
userId: userExample.id,
|
||||
provider: 'discord',
|
||||
providerId: id,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date()
|
||||
})
|
||||
const result = await oauthStrategy.callbackAddStrategy(
|
||||
{ id, name },
|
||||
{ accessToken: '123', current: userExample, currentStrategy: 'local' }
|
||||
)
|
||||
expect(prismaMock.oAuth.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
provider: 'discord',
|
||||
providerId: id
|
||||
}
|
||||
})
|
||||
expect(prismaMock.oAuth.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
userId: userExample.id,
|
||||
provider: 'discord',
|
||||
providerId: id
|
||||
}
|
||||
})
|
||||
expect(result).toEqual('success')
|
||||
})
|
||||
|
||||
it('should not add the strategy if the account of the provider is already used', async () => {
|
||||
const name = userExample.name
|
||||
const id = '12345'
|
||||
prismaMock.oAuth.findFirst.mockResolvedValue({
|
||||
id: 1,
|
||||
userId: 2,
|
||||
provider: 'discord',
|
||||
providerId: id,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date()
|
||||
})
|
||||
const result = await oauthStrategy.callbackAddStrategy(
|
||||
{ id, name },
|
||||
{ accessToken: '123', current: userExample, currentStrategy: 'local' }
|
||||
)
|
||||
expect(prismaMock.oAuth.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
provider: 'discord',
|
||||
providerId: id
|
||||
}
|
||||
})
|
||||
expect(prismaMock.oAuth.create).not.toHaveBeenCalled()
|
||||
expect(result).toEqual('This account is already used by someone else')
|
||||
})
|
||||
|
||||
it('should not add the strategy if the user is already connected with it', async () => {
|
||||
const name = userExample.name
|
||||
const id = '12345'
|
||||
prismaMock.oAuth.findFirst.mockResolvedValue({
|
||||
id: 1,
|
||||
userId: userExample.id,
|
||||
provider: 'discord',
|
||||
providerId: id,
|
||||
updatedAt: new Date(),
|
||||
createdAt: new Date()
|
||||
})
|
||||
const result = await oauthStrategy.callbackAddStrategy(
|
||||
{ id, name },
|
||||
{ accessToken: '123', current: userExample, currentStrategy: 'local' }
|
||||
)
|
||||
expect(prismaMock.oAuth.findFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
provider: 'discord',
|
||||
providerId: id
|
||||
}
|
||||
})
|
||||
expect(prismaMock.oAuth.create).not.toHaveBeenCalled()
|
||||
expect(result).toEqual('You are already using this account')
|
||||
})
|
||||
})
|
20
src/tools/utils/__test__/buildQueryURL.test.ts
Normal file
20
src/tools/utils/__test__/buildQueryURL.test.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { buildQueryURL } from '../buildQueryURL.js'
|
||||
|
||||
test('controllers/users/utils/buildQueryUrl', () => {
|
||||
expect(
|
||||
buildQueryURL('http://localhost:8080', {
|
||||
test: 'query'
|
||||
})
|
||||
).toEqual('http://localhost:8080/?test=query')
|
||||
expect(
|
||||
buildQueryURL('http://localhost:8080/', {
|
||||
test: 'query'
|
||||
})
|
||||
).toEqual('http://localhost:8080/?test=query')
|
||||
expect(
|
||||
buildQueryURL('http://localhost:3000', {
|
||||
test: 'query',
|
||||
code: 'abc'
|
||||
})
|
||||
).toEqual('http://localhost:3000/?test=query&code=abc')
|
||||
})
|
@ -1,7 +0,0 @@
|
||||
import { capitalize } from '../capitalize'
|
||||
|
||||
test('/tools/utils/capitalize', () => {
|
||||
expect(capitalize('hello world')).toBe('Hello world')
|
||||
expect('Test').toBe('Test')
|
||||
expect('TEST').toBe('TEST')
|
||||
})
|
@ -1,124 +0,0 @@
|
||||
import fsMock from 'mock-fs'
|
||||
import * as fsWithCallbacks from 'fs'
|
||||
|
||||
import {
|
||||
deleteAllFilesInDirectory,
|
||||
deleteFile,
|
||||
deleteMessages
|
||||
} from '../deleteFiles'
|
||||
import { messagesFilePath, usersLogoPath } from '../../configurations/constants'
|
||||
import Message from '../../../models/Message'
|
||||
import Guild from '../../../models/Guild'
|
||||
import Member from '../../../models/Member'
|
||||
import Channel from '../../../models/Channel'
|
||||
import User from '../../../models/User'
|
||||
|
||||
const fs = fsWithCallbacks.promises
|
||||
|
||||
describe('/tools/utils/deleteFiles - deleteAllFilesInDirectory', () => {
|
||||
it('delete all the files expect the directories', async () => {
|
||||
fsMock({
|
||||
'/files': {
|
||||
'default.png': '',
|
||||
'user-logo.png': '',
|
||||
'user-logo.jpg': '',
|
||||
directory: {
|
||||
file: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
await deleteAllFilesInDirectory('/files')
|
||||
const directoryContent = await fs.readdir('/files')
|
||||
expect(directoryContent.length).toEqual(1)
|
||||
expect(directoryContent[0]).toEqual('directory')
|
||||
})
|
||||
|
||||
it('delete all the files with all the directories recursively', async () => {
|
||||
fsMock({
|
||||
'/files': {
|
||||
'default.png': '',
|
||||
'user-logo.png': '',
|
||||
'user-logo.jpg': '',
|
||||
directory: {
|
||||
file: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
await deleteAllFilesInDirectory('/files', true)
|
||||
const filesDirectoryContent = await fs.readdir('/files')
|
||||
const directoryContent = await fs.readdir('/files/directory')
|
||||
expect(filesDirectoryContent.length).toEqual(1)
|
||||
expect(directoryContent.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('/tools/utils/deleteFiles - deleteFile', () => {
|
||||
it('should delete the file', async () => {
|
||||
fsMock({
|
||||
[usersLogoPath.filePath]: {
|
||||
'logo.png': ''
|
||||
}
|
||||
})
|
||||
await deleteFile({
|
||||
basePath: usersLogoPath,
|
||||
valueSavedInDatabase: `${usersLogoPath.name}/logo.png`
|
||||
})
|
||||
const directoryContent = await fs.readdir(usersLogoPath.filePath)
|
||||
expect(directoryContent.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('should not delete the default file', async () => {
|
||||
fsMock({
|
||||
[usersLogoPath.filePath]: {
|
||||
'logo.png': '',
|
||||
'default.png': ''
|
||||
}
|
||||
})
|
||||
await deleteFile({
|
||||
basePath: usersLogoPath,
|
||||
valueSavedInDatabase: `${usersLogoPath.name}/default.png`
|
||||
})
|
||||
const directoryContent = await fs.readdir(usersLogoPath.filePath)
|
||||
expect(directoryContent.length).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('/tools/utils/deleteFiles - deleteMessages', () => {
|
||||
it('should delete every messages and files', async () => {
|
||||
fsMock({
|
||||
[messagesFilePath.filePath]: {
|
||||
'logo.png': '',
|
||||
'random-file.mp3': '',
|
||||
'file-without-message': ''
|
||||
}
|
||||
})
|
||||
const user = await User.create({ name: 'John' })
|
||||
const guild = await Guild.create({ name: 'testing' })
|
||||
const channel = await Channel.create({
|
||||
name: 'general',
|
||||
isDefault: true,
|
||||
guildId: guild.id
|
||||
})
|
||||
const member = await Member.create({
|
||||
userId: user.id,
|
||||
guildId: guild.id,
|
||||
isOwner: true,
|
||||
lastVisitedChannelId: channel.id
|
||||
})
|
||||
const messagesToCreate = [
|
||||
{ value: `${messagesFilePath.name}/logo.png`, type: 'file' },
|
||||
{ value: `${messagesFilePath.name}/random-file.mp3`, type: 'file' }
|
||||
]
|
||||
const messages = messagesToCreate.map(async (message) => {
|
||||
return await Message.create({
|
||||
value: message.value,
|
||||
type: message.type,
|
||||
memberId: member.id,
|
||||
channelId: channel.id
|
||||
})
|
||||
})
|
||||
await deleteMessages(await Promise.all(messages))
|
||||
const directoryContent = await fs.readdir(messagesFilePath.filePath)
|
||||
expect(directoryContent.length).toEqual(1)
|
||||
})
|
||||
})
|
@ -1,9 +0,0 @@
|
||||
import { deleteObjectAttributes } from '../deleteObjectAttributes'
|
||||
|
||||
test('/tools/utils/deleteObjectAttributes', () => {
|
||||
const object = { attribute1: 'value1', attribute2: 'value2' }
|
||||
const hiddenObjectAttributes = ['attribute2']
|
||||
const result = deleteObjectAttributes(object, hiddenObjectAttributes)
|
||||
expect(result.attribute1).toEqual('value1')
|
||||
expect(result.attribute2).not.toBeDefined()
|
||||
})
|
@ -1,8 +0,0 @@
|
||||
import { parseIntOrDefaultValue } from '../parseIntOrDefaultValue'
|
||||
|
||||
test('/tools/utils/parseIntOrDefaultValue', () => {
|
||||
expect(parseIntOrDefaultValue('12', 10)).toEqual(12)
|
||||
expect(parseIntOrDefaultValue('shshsksk2', 10)).toEqual(10)
|
||||
expect(parseIntOrDefaultValue('', 10)).toEqual(10)
|
||||
expect(parseIntOrDefaultValue(' ', 10)).toEqual(10)
|
||||
})
|
@ -1,30 +0,0 @@
|
||||
import {
|
||||
randomCharacter,
|
||||
randomInteger,
|
||||
randomString,
|
||||
alphabet
|
||||
} from '../random'
|
||||
|
||||
describe('/tools/utils/random', () => {
|
||||
test('randomInteger', () => {
|
||||
const min = 1
|
||||
const max = 100
|
||||
const result = randomInteger(min, max)
|
||||
const isInteger = result % 1 === 0
|
||||
expect(isInteger).toBeTruthy()
|
||||
expect(result).toBeGreaterThanOrEqual(min)
|
||||
expect(result).toBeLessThanOrEqual(max)
|
||||
})
|
||||
|
||||
test('randomCharacter', () => {
|
||||
const result = randomCharacter()
|
||||
expect(result.length).toEqual(1)
|
||||
expect(alphabet.split('').includes(result))
|
||||
})
|
||||
|
||||
test('randomString', () => {
|
||||
const length = 7
|
||||
const result = randomString(length)
|
||||
expect(result.length).toEqual(length)
|
||||
})
|
||||
})
|
@ -1,106 +0,0 @@
|
||||
import fsMock from 'mock-fs'
|
||||
import * as fsWithCallbacks from 'fs'
|
||||
import { UploadedFile } from 'express-fileupload'
|
||||
|
||||
import { uploadImage } from '../uploadImage'
|
||||
import { PayloadTooLargeError } from '../../errors/PayloadTooLargeError'
|
||||
import { tempPath, usersLogoPath } from '../../configurations/constants'
|
||||
import { BadRequestError } from '../../errors/BadRequestError'
|
||||
|
||||
const fs = fsWithCallbacks.promises
|
||||
|
||||
const imagesPath = usersLogoPath.filePath
|
||||
|
||||
const getImage = (
|
||||
props: { truncated?: boolean, mimetype?: string } = {}
|
||||
): UploadedFile => {
|
||||
const { truncated = false, mimetype = 'image/png' } = props
|
||||
return {
|
||||
name: 'logo',
|
||||
mv: jest.fn(),
|
||||
encoding: 'utf-8',
|
||||
mimetype,
|
||||
data: Buffer.from([]),
|
||||
tempFilePath: '/temp/logo.png',
|
||||
truncated,
|
||||
size: 1024,
|
||||
md5: '12345abcd'
|
||||
}
|
||||
}
|
||||
|
||||
describe('/tools/utils/uploadImage', () => {
|
||||
it('should succeeds and save the image', async () => {
|
||||
fsMock({
|
||||
[tempPath]: {
|
||||
'logo.png': ''
|
||||
},
|
||||
[imagesPath]: {
|
||||
'logo.png': ''
|
||||
}
|
||||
})
|
||||
const image = getImage()
|
||||
const result = await uploadImage({
|
||||
image,
|
||||
propertyName: 'logo',
|
||||
oldImage: '/images/logo.png',
|
||||
imagesPath
|
||||
})
|
||||
expect(result).not.toBeNull()
|
||||
expect(image.mv).toHaveBeenCalled()
|
||||
const directoryContent = await fs.readdir(tempPath)
|
||||
expect(directoryContent.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('should returns null with undefined image file(s)', async () => {
|
||||
const result = await uploadImage({
|
||||
image: [getImage(), getImage()],
|
||||
propertyName: 'logo',
|
||||
oldImage: '/images/logo.png',
|
||||
imagesPath
|
||||
})
|
||||
const result2 = await uploadImage({
|
||||
image: undefined,
|
||||
propertyName: 'logo',
|
||||
oldImage: '/images/logo.png',
|
||||
imagesPath
|
||||
})
|
||||
expect(result).toBeNull()
|
||||
expect(result2).toBeNull()
|
||||
})
|
||||
|
||||
it('should fails if the file is over the size limit', async () => {
|
||||
fsMock({
|
||||
[tempPath]: {
|
||||
'logo.png': ''
|
||||
}
|
||||
})
|
||||
await expect(
|
||||
uploadImage({
|
||||
image: getImage({ truncated: true }),
|
||||
propertyName: 'logo',
|
||||
oldImage: '/images/logo.png',
|
||||
imagesPath
|
||||
})
|
||||
).rejects.toThrow(PayloadTooLargeError)
|
||||
const directoryContent = await fs.readdir(tempPath)
|
||||
expect(directoryContent.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('should fails if the file is not an image', async () => {
|
||||
fsMock({
|
||||
[tempPath]: {
|
||||
'logo.png': ''
|
||||
}
|
||||
})
|
||||
await expect(
|
||||
uploadImage({
|
||||
image: getImage({ mimetype: 'text/html' }),
|
||||
propertyName: 'logo',
|
||||
oldImage: '/images/logo.png',
|
||||
imagesPath
|
||||
})
|
||||
).rejects.toThrow(BadRequestError)
|
||||
const directoryContent = await fs.readdir(tempPath)
|
||||
expect(directoryContent.length).toEqual(0)
|
||||
})
|
||||
})
|
16
src/tools/utils/buildQueryURL.ts
Normal file
16
src/tools/utils/buildQueryURL.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { URL } from 'node:url'
|
||||
|
||||
export interface ObjectAny {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export const buildQueryURL = (
|
||||
baseURL: string,
|
||||
queryObject: ObjectAny
|
||||
): string => {
|
||||
const url = new URL(baseURL)
|
||||
Object.entries(queryObject).forEach(([query, value]) => {
|
||||
url.searchParams.append(query, value)
|
||||
})
|
||||
return url.href
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
/** converts the first character of the string to capital (uppercase) letter */
|
||||
export function capitalize (s: string): string {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1)
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
import * as fsWithCallbacks from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import {
|
||||
guildsIconPath,
|
||||
messagesFilePath,
|
||||
usersLogoPath
|
||||
} from '../configurations/constants'
|
||||
import Message from '../../models/Message'
|
||||
|
||||
const fs = fsWithCallbacks.promises
|
||||
|
||||
export const deleteAllFilesInDirectory = async (
|
||||
directoryPath: string,
|
||||
isRecursive: boolean = false
|
||||
): Promise<void> => {
|
||||
const files = await fs.readdir(directoryPath)
|
||||
for (const file of files) {
|
||||
const filePath = path.resolve(directoryPath, file)
|
||||
const stats = await fs.stat(filePath)
|
||||
if (stats.isFile()) {
|
||||
await fs.unlink(filePath)
|
||||
} else if (isRecursive && stats.isDirectory()) {
|
||||
await deleteAllFilesInDirectory(filePath, isRecursive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BasePath =
|
||||
| typeof guildsIconPath
|
||||
| typeof usersLogoPath
|
||||
| typeof messagesFilePath
|
||||
|
||||
export const deleteFile = async (options: {
|
||||
basePath: BasePath
|
||||
/** @example '/uploads/users/logo.png' */
|
||||
valueSavedInDatabase: string
|
||||
}): Promise<void> => {
|
||||
const { basePath, valueSavedInDatabase: value } = options
|
||||
if (value !== `${basePath.name}/default.png`) {
|
||||
const filePath = value.split('/')
|
||||
const filename = filePath[filePath.length - 1]
|
||||
await fs.unlink(path.join(basePath.filePath, filename))
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteMessages = async (messages: Message[]): Promise<void> => {
|
||||
for (const message of messages) {
|
||||
if (message.type === 'file') {
|
||||
await deleteFile({
|
||||
basePath: messagesFilePath,
|
||||
valueSavedInDatabase: message.value
|
||||
})
|
||||
}
|
||||
await message.destroy()
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { ObjectAny } from '../../typings/utils'
|
||||
|
||||
export const deleteObjectAttributes = (
|
||||
object: ObjectAny,
|
||||
attributesToDelete: readonly string[]
|
||||
): ObjectAny => {
|
||||
const map = new Map(Object.entries(object))
|
||||
for (const attribute of attributesToDelete) {
|
||||
map.delete(attribute)
|
||||
}
|
||||
return Object.fromEntries(map)
|
||||
}
|
44
src/tools/utils/jwtToken.ts
Normal file
44
src/tools/utils/jwtToken.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import ms from 'ms'
|
||||
|
||||
import prisma from '../database/prisma.js'
|
||||
import { UserJWT } from '../../models/User.js'
|
||||
import {
|
||||
JWT_ACCESS_EXPIRES_IN,
|
||||
JWT_ACCESS_SECRET,
|
||||
JWT_REFRESH_SECRET
|
||||
} from '../configurations/index.js'
|
||||
|
||||
export interface ResponseJWT {
|
||||
accessToken: string
|
||||
refreshToken?: string
|
||||
expiresIn: number
|
||||
type: 'Bearer'
|
||||
}
|
||||
|
||||
export const jwtSchema = {
|
||||
accessToken: Type.String(),
|
||||
refreshToken: Type.String(),
|
||||
expiresIn: Type.Integer({
|
||||
description:
|
||||
'expiresIn is how long, in milliseconds, until the returned accessToken expires'
|
||||
}),
|
||||
type: Type.Literal('Bearer')
|
||||
}
|
||||
|
||||
export const expiresIn = ms(JWT_ACCESS_EXPIRES_IN)
|
||||
|
||||
export const generateAccessToken = (user: UserJWT): string => {
|
||||
return jwt.sign(user, JWT_ACCESS_SECRET, {
|
||||
expiresIn: JWT_ACCESS_EXPIRES_IN
|
||||
})
|
||||
}
|
||||
|
||||
export const generateRefreshToken = async (user: UserJWT): Promise<string> => {
|
||||
const refreshToken = jwt.sign(user, JWT_REFRESH_SECRET)
|
||||
await prisma.refreshToken.create({
|
||||
data: { token: refreshToken, userId: user.id }
|
||||
})
|
||||
return refreshToken
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/** returns the defaultValue provided, if parseInt(value) return NaN */
|
||||
export function parseIntOrDefaultValue (
|
||||
value: string | undefined,
|
||||
defaultValue: number
|
||||
): number {
|
||||
const valueInteger = parseInt(value as string, 10)
|
||||
if (value != null && !isNaN(valueInteger)) {
|
||||
return valueInteger
|
||||
}
|
||||
return defaultValue
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
export const alphabet =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
|
||||
export function randomInteger (min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
export function randomCharacter (): string {
|
||||
return alphabet.charAt(randomInteger(0, alphabet.length - 1))
|
||||
}
|
||||
|
||||
export function randomString (length: number): string {
|
||||
return Array.from({ length })
|
||||
.map(randomCharacter)
|
||||
.join('')
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import { UploadedFile } from 'express-fileupload'
|
||||
import * as fsWithCallbacks from 'fs'
|
||||
import path from 'path'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import {
|
||||
commonErrorsMessages,
|
||||
supportedImageMimetype,
|
||||
tempPath
|
||||
} from '../configurations/constants'
|
||||
import { deleteAllFilesInDirectory } from './deleteFiles'
|
||||
import { BadRequestError } from '../errors/BadRequestError'
|
||||
import { PayloadTooLargeError } from '../errors/PayloadTooLargeError'
|
||||
|
||||
const fs = fsWithCallbacks.promises
|
||||
|
||||
interface UploadImageOptions {
|
||||
image: UploadedFile | UploadedFile[] | undefined
|
||||
propertyName: string
|
||||
oldImage: string
|
||||
imagesPath: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Handle upload of an image
|
||||
* @returns the complete image name if success otherwise null
|
||||
*/
|
||||
export const uploadImage = async (
|
||||
options: UploadImageOptions
|
||||
): Promise<string | null> => {
|
||||
const { image, propertyName, oldImage, imagesPath } = options
|
||||
if (image != null && !Array.isArray(image)) {
|
||||
if (image.truncated) {
|
||||
await deleteAllFilesInDirectory(tempPath)
|
||||
throw new PayloadTooLargeError(
|
||||
commonErrorsMessages.image.tooLarge(propertyName)
|
||||
)
|
||||
}
|
||||
if (!supportedImageMimetype.includes(image.mimetype)) {
|
||||
await deleteAllFilesInDirectory(tempPath)
|
||||
throw new BadRequestError(
|
||||
commonErrorsMessages.image.validType(propertyName)
|
||||
)
|
||||
}
|
||||
const splitedMimetype = image.mimetype.split('/')
|
||||
const imageExtension = splitedMimetype[1]
|
||||
const completeImageName = `${uuidv4()}.${imageExtension}`
|
||||
const oldImagePath = oldImage.split('/')
|
||||
const oldImageName = oldImagePath[oldImagePath.length - 1]
|
||||
if (!oldImageName.startsWith('default')) {
|
||||
await fs.unlink(path.join(imagesPath, oldImageName))
|
||||
}
|
||||
await image.mv(path.join(imagesPath, completeImageName))
|
||||
await deleteAllFilesInDirectory(tempPath)
|
||||
return completeImageName
|
||||
}
|
||||
return null
|
||||
}
|
Reference in New Issue
Block a user