ajout app

This commit is contained in:
2024-04-17 20:22:30 +02:00
parent cc017cfc5e
commit f9d05a2fd3
8025 changed files with 729805 additions and 0 deletions

View File

@ -0,0 +1,3 @@
export function newDateTime() {
return new Date().toISOString()
}

View File

@ -0,0 +1,8 @@
import fs from 'fs'
import dotenv from 'dotenv'
if (fs.existsSync('.env.local')) {
dotenv.config({ path: '.env.local' })
} else {
dotenv.config()
}

View File

@ -0,0 +1,119 @@
import { Request, Response, NextFunction } from 'express'
export enum ErrorCode {
InternalServerError = 'internal_server_error',
RequestValidationError = 'request_validation_error',
UsernameUnavailable = 'username_unavailable',
ResourceNotFound = 'resource_not_found',
InvalidUsernameOrPassword = 'invalid_username_or_password',
InvalidPassword = 'invalid_password',
InvalidJwt = 'invalid_jwt',
EmailNotConfimed = 'email_not_confirmed',
RefreshTokenExpired = 'refresh_token_expired',
InvalidRequest = 'invalid_request',
UnsupportedGrantType = 'unsupported_grant_type',
PasswordValidationFailed = 'password_validation_failed',
}
const statuses: { [key: string]: number } = {
[ErrorCode.InternalServerError]: 500,
[ErrorCode.RequestValidationError]: 400,
[ErrorCode.UsernameUnavailable]: 409,
[ErrorCode.ResourceNotFound]: 404,
[ErrorCode.InvalidUsernameOrPassword]: 401,
[ErrorCode.InvalidPassword]: 401,
[ErrorCode.InvalidJwt]: 401,
[ErrorCode.EmailNotConfimed]: 401,
[ErrorCode.RefreshTokenExpired]: 401,
[ErrorCode.InvalidRequest]: 400,
[ErrorCode.UnsupportedGrantType]: 400,
[ErrorCode.PasswordValidationFailed]: 400,
}
const userMessages: { [key: string]: string } = {
[ErrorCode.UsernameUnavailable]: 'Email belongs to an existing user.',
[ErrorCode.EmailNotConfimed]: 'Email not confirmed.',
[ErrorCode.InvalidPassword]: 'Invalid password.',
[ErrorCode.InvalidUsernameOrPassword]: 'Invalid username or password.',
}
export type ErrorData = {
code: string
status: number
message: string
userMessage: string
moreInfo: string
error?: any
}
export type ErrorResponse = {
code: string
status: number
message: string
userMessage: string
moreInfo: string
}
export type ErrorOptions = {
code: ErrorCode
message?: string
userMessage?: string
error?: any
}
export function newError(options: ErrorOptions): ErrorData {
const userMessage =
options.userMessage ||
userMessages[options.code] ||
'Oops! something went wrong'
return {
code: options.code,
status: statuses[options.code],
message: options.message || userMessage,
userMessage,
moreInfo: `https://voltaserve.com/docs/idp/errors/${options.code}`,
error: options.error,
}
}
export function newResponse(data: ErrorData): ErrorResponse {
return {
code: data.code,
status: data.status,
message: data.message,
userMessage: data.userMessage,
moreInfo: data.moreInfo,
}
}
export function errorHandler(
error: any,
_: Request,
res: Response,
next: NextFunction
) {
if (error.code && Object.values(ErrorCode).includes(error.code)) {
const data = error as ErrorData
if (data.error) {
console.error(data.error)
}
res.status(data.status).json(newResponse(data))
} else {
console.error(error)
res
.status(500)
.json(newResponse(newError({ code: ErrorCode.InternalServerError })))
}
next(error)
return
}
export function parseValidationError(result: any): ErrorData {
let message: string
if (result.errors) {
message = result.errors
.map((e: any) => `${e.msg} for ${e.type} ${e.path} in ${e.location}.`)
.join(' ')
}
return newError({ code: ErrorCode.RequestValidationError, message })
}

View File

@ -0,0 +1,10 @@
import { v4 as uuidv4 } from 'uuid'
import hashids from 'hashids'
export function newHashId(): string {
return new hashids(uuidv4()).encode(Date.now())
}
export function newHyphenlessUuid(): string {
return uuidv4().replaceAll('-', '')
}

View File

@ -0,0 +1,59 @@
import fs from 'fs'
import Handlebars from 'handlebars'
import yaml from 'js-yaml'
import nodemailer from 'nodemailer'
import path from 'path'
import { getConfig } from '@/config/config'
type MessageParams = {
subject: string
}
const config = getConfig().smtp
const transporter = nodemailer.createTransport({
host: config.host,
port: config.port,
secure: config.secure,
auth:
config.username || config.password
? {
user: config.username,
pass: config.password,
}
: null,
})
export function sendTemplateMail(
template: string,
address: string,
variables: Record<string, any>
) {
const params = yaml.load(
fs.readFileSync(path.join('templates', template, 'params.yml'), 'utf8')
) as MessageParams
const html = Handlebars.compile(
fs.readFileSync(path.join('templates', template, 'template.hbs'), 'utf8')
)(variables)
const text = Handlebars.compile(
fs.readFileSync(path.join('templates', template, 'template.txt'), 'utf8')
)(variables)
return new Promise<void>((resolve, reject) => {
transporter.sendMail(
{
from: `"${config.senderName}" <${config.senderAddress}>`,
to: address,
subject: params.subject,
text,
html,
},
(err) => {
if (err) {
reject(err)
} else {
resolve()
}
}
)
})
}

View File

@ -0,0 +1,6 @@
import { Request } from 'express'
import { User } from '@/user/repo'
export interface PassportRequest extends Request {
user: User
}

View File

@ -0,0 +1,13 @@
import { scryptSync, randomBytes } from 'crypto'
export function hashPassword(password: string): string {
const salt = randomBytes(16).toString('hex')
const key = scryptSync(password, salt, 64).toString('hex')
return `${key}:${salt}`
}
export function verifyPassword(password: string, hash: string): boolean {
const [key, salt] = hash.split(':')
const newKey = scryptSync(password, salt, 64).toString('hex')
return newKey === key
}

View File

@ -0,0 +1,6 @@
import { Client } from 'pg'
import { getConfig } from '@/config/config'
export const client = new Client({
connectionString: getConfig().databaseURL,
})

View File

@ -0,0 +1,9 @@
import { MeiliSearch } from 'meilisearch'
import { getConfig } from '@/config/config'
export const USER_SEARCH_INDEX = 'user'
const client = new MeiliSearch({ host: getConfig().search.url })
client.createIndex(USER_SEARCH_INDEX, { primaryKey: 'id' })
export default client