Skip to content

Commit

Permalink
Upgrade v5 (#30)
Browse files Browse the repository at this point in the history
* chore: upgrade fastify related packages

* chore: update fastify non-related packages

* fix: eslint errors
  • Loading branch information
jean-michelet authored Sep 21, 2024
1 parent 441a123 commit 7e524a5
Show file tree
Hide file tree
Showing 34 changed files with 720 additions and 732 deletions.
10 changes: 5 additions & 5 deletions @types/fastify/fastify.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Auth } from "../../src/schemas/auth.ts";
import { Auth } from '../../src/schemas/auth.ts'

declare module "fastify" {
export interface FastifyRequest {
user: Auth
}
declare module 'fastify' {
export interface FastifyRequest {
user: Auth
}
}
2 changes: 1 addition & 1 deletion @types/node/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ declare global {
}
}

export {};
export {}
44 changes: 22 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,32 @@
"author": "Michelet Jean <jean.antoine.michelet@gmail.com>",
"license": "MIT",
"dependencies": {
"@fastify/autoload": "^5.10.0",
"@fastify/cors": "^9.0.1",
"@fastify/env": "^4.3.0",
"@fastify/helmet": "^11.1.1",
"@fastify/jwt": "^8.0.1",
"@fastify/mysql": "^4.3.0",
"@fastify/rate-limit": "^9.1.0",
"@fastify/sensible": "^5.0.0",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^4.0.1",
"@fastify/type-provider-typebox": "^4.0.0",
"@fastify/under-pressure": "^8.3.0",
"@sinclair/typebox": "^0.33.7",
"@fastify/autoload": "^6.0.0",
"@fastify/cors": "^10.0.0",
"@fastify/env": "^5.0.1",
"@fastify/helmet": "^12.0.0",
"@fastify/jwt": "^9.0.0",
"@fastify/mysql": "^5.0.1",
"@fastify/rate-limit": "^10.0.1",
"@fastify/sensible": "^6.0.1",
"@fastify/swagger": "^9.0.0",
"@fastify/swagger-ui": "^5.0.1",
"@fastify/type-provider-typebox": "^5.0.0",
"@fastify/under-pressure": "^9.0.1",
"@sinclair/typebox": "^0.33.12",
"concurrently": "^9.0.1",
"fastify": "^4.26.1",
"fastify-cli": "^6.1.1",
"fastify-plugin": "^4.0.0",
"postgrator": "^7.2.0"
"fastify": "^5.0.0",
"fastify-cli": "^7.0.0",
"fastify-plugin": "^5.0.1",
"postgrator": "^7.3.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"eslint": "^9.4.0",
"@types/node": "^22.5.5",
"eslint": "^9.11.0",
"fastify-tsconfig": "^2.0.0",
"mysql2": "^3.10.1",
"neostandard": "^0.7.0",
"mysql2": "^3.11.3",
"neostandard": "^0.11.5",
"tap": "^21.0.1",
"typescript": "^5.4.5"
"typescript": "^5.6.2"
}
}
81 changes: 40 additions & 41 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,47 @@
* If you would like to turn your application into a standalone executable, look at server.js file
*/

import path from "node:path";
import fastifyAutoload from "@fastify/autoload";
import { FastifyInstance, FastifyPluginOptions } from "fastify";
import path from 'node:path'
import fastifyAutoload from '@fastify/autoload'
import { FastifyInstance, FastifyPluginOptions } from 'fastify'

export const options = {
ajv: {
customOptions: {
coerceTypes: "array",
removeAdditional: "all"
coerceTypes: 'array',
removeAdditional: 'all'
}
}
};
}

export default async function serviceApp(
export default async function serviceApp (
fastify: FastifyInstance,
opts: FastifyPluginOptions
) {
delete opts.skipOverride // This option only serves testing purpose
// This loads all external plugins defined in plugins/external
// those should be registered first as your custom plugins might depend on them
await fastify.register(fastifyAutoload, {
dir: path.join(import.meta.dirname, "plugins/external"),
dir: path.join(import.meta.dirname, 'plugins/external'),
options: { ...opts }
});
})

// This loads all your custom plugins defined in plugins/custom
// those should be support plugins that are reused
// through your application
fastify.register(fastifyAutoload, {
dir: path.join(import.meta.dirname, "plugins/custom"),
dir: path.join(import.meta.dirname, 'plugins/custom'),
options: { ...opts }
});
})

// This loads all plugins defined in routes
// define your routes in one of these
fastify.register(fastifyAutoload, {
dir: path.join(import.meta.dirname, "routes"),
dir: path.join(import.meta.dirname, 'routes'),
autoHooks: true,
cascadeHooks: true,
options: { ...opts }
});
})

fastify.setErrorHandler((err, request, reply) => {
request.log.error(
Expand All @@ -55,43 +55,42 @@ export default async function serviceApp(
params: request.params
}
},
"Unhandled error occurred"
);
'Unhandled error occurred'
)

reply.code(err.statusCode ?? 500);
reply.code(err.statusCode ?? 500)

let message = "Internal Server Error";
let message = 'Internal Server Error'
if (err.statusCode === 401) {
message = err.message;
message = err.message
}

return { message };
});
return { message }
})

// An attacker could search for valid URLs if your 404 error handling is not rate limited.
fastify.setNotFoundHandler(
{
preHandler: fastify.rateLimit({
max: 3,
timeWindow: 500
})
},
{
preHandler: fastify.rateLimit({
max: 3,
timeWindow: 500
})
},
(request, reply) => {
request.log.warn(
{
request: {
method: request.method,
url: request.url,
query: request.query,
params: request.params
}
},
'Resource not found'
)

request.log.warn(
{
request: {
method: request.method,
url: request.url,
query: request.query,
params: request.params
}
},
"Resource not found"
);

reply.code(404);
reply.code(404)

return { message: "Not Found" };
});
return { message: 'Not Found' }
})
}
96 changes: 48 additions & 48 deletions src/plugins/custom/repository.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,96 @@
import { MySQLPromisePool } from "@fastify/mysql";
import { FastifyInstance } from "fastify";
import fp from "fastify-plugin";
import { RowDataPacket, ResultSetHeader } from "mysql2";
import { MySQLPromisePool } from '@fastify/mysql'
import { FastifyInstance } from 'fastify'
import fp from 'fastify-plugin'
import { RowDataPacket, ResultSetHeader } from 'mysql2'

declare module "fastify" {
declare module 'fastify' {
export interface FastifyInstance {
repository: Repository;
}
}

export type Repository = MySQLPromisePool & ReturnType<typeof createRepository>;
export type Repository = MySQLPromisePool & ReturnType<typeof createRepository>

type QuerySeparator = 'AND' | ',';
type QuerySeparator = 'AND' | ','

type QueryOptions = {
select?: string;
where?: Record<string, any>;
};
}

type WriteOptions = {
data: Record<string, any>;
where?: Record<string, any>;
};
}

function createRepository(fastify: FastifyInstance) {
function createRepository (fastify: FastifyInstance) {
const processAssignmentRecord = (record: Record<string, any>, separator: QuerySeparator) => {
const keys = Object.keys(record);
const values = Object.values(record);
const clause = keys.map((key) => `${key} = ?`).join(` ${separator} `);
const keys = Object.keys(record)
const values = Object.values(record)
const clause = keys.map((key) => `${key} = ?`).join(` ${separator} `)

return [clause, values] as const;
};
return [clause, values] as const
}

const repository = {
...fastify.mysql,
find: async <T>(table: string, opts: QueryOptions): Promise<T | null> => {
const { select = '*', where = {1:1} } = opts;
const [clause, values] = processAssignmentRecord(where, 'AND');
const { select = '*', where = { 1: 1 } } = opts
const [clause, values] = processAssignmentRecord(where, 'AND')

const query = `SELECT ${select} FROM ${table} WHERE ${clause} LIMIT 1`;
const [rows] = await fastify.mysql.query<RowDataPacket[]>(query, values);
const query = `SELECT ${select} FROM ${table} WHERE ${clause} LIMIT 1`
const [rows] = await fastify.mysql.query<RowDataPacket[]>(query, values)
if (rows.length < 1) {
return null;
return null
}

return rows[0] as T;
return rows[0] as T
},

findMany: async <T>(table: string, opts: QueryOptions = {}): Promise<T[]> => {
const { select = '*', where = {1:1} } = opts;
const [clause, values] = processAssignmentRecord(where, 'AND');
const { select = '*', where = { 1: 1 } } = opts
const [clause, values] = processAssignmentRecord(where, 'AND')

const query = `SELECT ${select} FROM ${table} WHERE ${clause}`;
const [rows] = await fastify.mysql.query<RowDataPacket[]>(query, values);
const query = `SELECT ${select} FROM ${table} WHERE ${clause}`
const [rows] = await fastify.mysql.query<RowDataPacket[]>(query, values)

return rows as T[];
return rows as T[]
},

create: async (table: string, opts: WriteOptions): Promise<number> => {
const { data } = opts;
const columns = Object.keys(data).join(', ');
const placeholders = Object.keys(data).map(() => '?').join(', ');
const values = Object.values(data);
const { data } = opts
const columns = Object.keys(data).join(', ')
const placeholders = Object.keys(data).map(() => '?').join(', ')
const values = Object.values(data)

const query = `INSERT INTO ${table} (${columns}) VALUES (${placeholders})`;
const [result] = await fastify.mysql.query<ResultSetHeader>(query, values);
const query = `INSERT INTO ${table} (${columns}) VALUES (${placeholders})`
const [result] = await fastify.mysql.query<ResultSetHeader>(query, values)

return result.insertId;
return result.insertId
},

update: async (table: string, opts: WriteOptions): Promise<number> => {
const { data, where = {} } = opts;
const [dataClause, dataValues] = processAssignmentRecord(data, ',');
const [whereClause, whereValues] = processAssignmentRecord(where, 'AND');
const { data, where = {} } = opts
const [dataClause, dataValues] = processAssignmentRecord(data, ',')
const [whereClause, whereValues] = processAssignmentRecord(where, 'AND')

const query = `UPDATE ${table} SET ${dataClause} WHERE ${whereClause}`;
const [result] = await fastify.mysql.query<ResultSetHeader>(query, [...dataValues, ...whereValues]);
const query = `UPDATE ${table} SET ${dataClause} WHERE ${whereClause}`
const [result] = await fastify.mysql.query<ResultSetHeader>(query, [...dataValues, ...whereValues])

return result.affectedRows;
return result.affectedRows
},

delete: async (table: string, where: Record<string, any>): Promise<number> => {
const [clause, values] = processAssignmentRecord(where, 'AND');
const [clause, values] = processAssignmentRecord(where, 'AND')

const query = `DELETE FROM ${table} WHERE ${clause}`;
const [result] = await fastify.mysql.query<ResultSetHeader>(query, values);
const query = `DELETE FROM ${table} WHERE ${clause}`
const [result] = await fastify.mysql.query<ResultSetHeader>(query, values)

return result.affectedRows;
return result.affectedRows
}
};
}

return repository;
return repository
}

/**
Expand All @@ -101,9 +101,9 @@ function createRepository(fastify: FastifyInstance) {
*/
export default fp(
async function (fastify) {
fastify.decorate("repository", createRepository(fastify));
fastify.decorate('repository', createRepository(fastify))
// You should name your plugins if you want to avoid name collisions
// and/or to perform dependency checks.
},
{ name: "repository", dependencies: ['mysql'] }
);
{ name: 'repository', dependencies: ['mysql'] }
)
Loading

0 comments on commit 7e524a5

Please sign in to comment.