Compare commits
11 Commits
d6cfef2a62
...
72d310ccfa
Author | SHA1 | Date | |
---|---|---|---|
72d310ccfa | |||
cf548890fc | |||
580a4a4320 | |||
2cb9c142c4 | |||
92e0fd2a68 | |||
17cccc0c9f | |||
9f8582c412 | |||
ad0f30876e | |||
db40b772f1 | |||
ddd9a2fef4 | |||
c12439d1ed |
9
.env.example
Normal file
9
.env.example
Normal file
@ -0,0 +1,9 @@
|
||||
HASH_SECRET=''
|
||||
JWT_SECRET=''
|
||||
PROJECT_NAME=''
|
||||
|
||||
MYSQL_HOST=''
|
||||
MYSQL_PORT=''
|
||||
MYSQL_USERNAME=''
|
||||
MYSQL_PASS=''
|
||||
MYSQL_DATABASE=''
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="82" name="TypeScript" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/jsLibraryMappings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
25
.idea/jsonSchemas.xml
generated
Normal file
25
.idea/jsonSchemas.xml
generated
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||
<state>
|
||||
<map>
|
||||
<entry key="TSConfig">
|
||||
<value>
|
||||
<SchemaInfo>
|
||||
<option name="name" value="TSConfig" />
|
||||
<option name="relativePathToSchema" value="http://json.schemastore.org/tsconfig" />
|
||||
<option name="applicationDefined" value="true" />
|
||||
<option name="patterns">
|
||||
<list>
|
||||
<Item>
|
||||
<option name="path" value="tsconfig.json" />
|
||||
</Item>
|
||||
</list>
|
||||
</option>
|
||||
</SchemaInfo>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</state>
|
||||
</component>
|
||||
</project>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="ASK" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/template-express.iml" filepath="$PROJECT_DIR$/.idea/template-express.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
12
.idea/template-express.iml
generated
Normal file
12
.idea/template-express.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
24
biome.json
Normal file
24
biome.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.6.4/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"performance": {
|
||||
"recommended": true,
|
||||
"noDelete": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"useLiteralKeys": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 15
|
||||
}
|
||||
}
|
28
package.json
Normal file
28
package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "brief-05-back",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/app.js",
|
||||
"keywords": [],
|
||||
"author": "Mathis HERRIOT",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@node-rs/argon2": "^1.8.3",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"express-xss-sanitizer": "^1.2.0",
|
||||
"jose": "^5.2.4",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.9.7",
|
||||
"tslog": "^4.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.7.0",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.12.7"
|
||||
}
|
||||
}
|
27
src/app.ts
Normal file
27
src/app.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import express, { type Express } from 'express';
|
||||
import cors from 'cors';
|
||||
import compression from 'compression';
|
||||
import {Logger} from "tslog";
|
||||
|
||||
|
||||
const logger = new Logger({ name: "App" });
|
||||
|
||||
const app: Express = express();
|
||||
|
||||
// enable cors
|
||||
app.use(cors());
|
||||
app.options('*', cors());
|
||||
|
||||
// parse json request body
|
||||
app.use(express.json());
|
||||
|
||||
// parse urlencoded request body
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// gzip compression
|
||||
app.use(compression())
|
||||
|
||||
//app.use('/auth', AuthRoutes)
|
||||
|
||||
//app.listen(3333)
|
||||
logger.info('Server is running !')
|
23
src/interfaces/UserData.ts
Normal file
23
src/interfaces/UserData.ts
Normal file
@ -0,0 +1,23 @@
|
||||
interface DbUserData {
|
||||
id?: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
|
||||
passwordHash: string;
|
||||
|
||||
isAdmin: boolean;
|
||||
isDisabled: boolean;
|
||||
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpires?: Date;
|
||||
|
||||
dob: Date;
|
||||
gdpr: Date;
|
||||
iat: Date;
|
||||
uat: Date;
|
||||
}
|
||||
|
||||
export default DbUserData
|
1
src/interfaces/index.ts
Normal file
1
src/interfaces/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './UserData'
|
2
src/services/index.ts
Normal file
2
src/services/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './jwt.service';
|
||||
export * from './mysql.service'
|
60
src/services/jwt.service.ts
Normal file
60
src/services/jwt.service.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import Jose, {type JWTHeaderParameters, type JWTPayload} from "jose";
|
||||
import {Logger} from "tslog";
|
||||
|
||||
const logger = new Logger({ name: "JwtService" });
|
||||
|
||||
/**
|
||||
* Verify a JWT token.
|
||||
*
|
||||
* @param {string | Uint8Array} jwt
|
||||
* - The JWT token to verify.
|
||||
* @returns {Promise<null | object>}
|
||||
* - The payload of the verified JWT token or null if verification fails.
|
||||
*/
|
||||
async function JwtVerifyService(jwt: string | Uint8Array): Promise<null | object> {
|
||||
try {
|
||||
const result = await Jose.jwtVerify(
|
||||
jwt,
|
||||
new TextEncoder()
|
||||
.encode(`${process.env["JWT_SECRET"]}`),
|
||||
{
|
||||
})
|
||||
return result.payload;
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously signs a JWT token using the provided payload, header, expiration time, and audience.
|
||||
*
|
||||
* @param {JWTPayload} payload
|
||||
* - The payload data to include in the JWT token.
|
||||
* @param {JWTHeaderParameters} pHeader
|
||||
* - The protected header parameters for the JWT token.
|
||||
* @param {string | number | Date} expTime
|
||||
* - The expiration time for the JWT token. (Can be expressed with '1d', '1mo'...)
|
||||
* @param {string | string[]} audience
|
||||
* - The intended audience for the JWT token.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* - A promise that resolves with the signed JWT token.
|
||||
*/
|
||||
async function JwtSignService(payload: JWTPayload, pHeader: JWTHeaderParameters, expTime: string | number | Date, audience: string | string[]): Promise<string> {
|
||||
return await new Jose.SignJWT(payload)
|
||||
.setProtectedHeader(pHeader)
|
||||
.setIssuedAt(new Date())
|
||||
.setIssuer(`${process.env["JWT_SECRET"]} - Mathis HERRIOT`)
|
||||
.setAudience(audience)
|
||||
.setExpirationTime(expTime)
|
||||
.sign(new TextEncoder().encode(`${process.env["JWT_SECRET"]}`))
|
||||
}
|
||||
|
||||
const JwtService = {
|
||||
verify: JwtVerifyService,
|
||||
sign: JwtSignService
|
||||
}
|
||||
|
||||
export default JwtService
|
234
src/services/mysql.service.ts
Normal file
234
src/services/mysql.service.ts
Normal file
@ -0,0 +1,234 @@
|
||||
import mysql, {type Connection, type ConnectionOptions} from 'mysql2';
|
||||
import {Logger} from "tslog";
|
||||
// biome-ignore lint/style/useImportType: <explanation>
|
||||
import DbUserData from "@interfaces/UserData";
|
||||
|
||||
|
||||
const access: ConnectionOptions = {
|
||||
host: `${process.env["MYSQL_HOST"]}`,
|
||||
port: Number.parseInt(`${process.env["MYSQL_PORT"]}`),
|
||||
user: `${process.env["MYSQL_USER"]}`,
|
||||
database: `${process.env["MYSQL_USER"]}`,
|
||||
password: `${process.env["MYSQL_PASS"]}`
|
||||
};
|
||||
|
||||
|
||||
class MySqlHandler {
|
||||
private readonly handlerName: string;
|
||||
private Logger: Logger<unknown>
|
||||
private Connection: Connection;
|
||||
|
||||
constructor(handlerName?: string) {
|
||||
this.handlerName = handlerName || 'Unknown';
|
||||
this.Logger = new Logger({ name: `DB>> ${this.handlerName}` });
|
||||
this.Connection = mysql.createConnection(access);
|
||||
this.Connection.connect((err) => {
|
||||
if (err) {
|
||||
this.Logger.error(`Error connecting to MySQL: ${err}`);
|
||||
throw new Error()
|
||||
}
|
||||
this.Logger.info(`Connected to MySQL database (${access.database})`);
|
||||
});
|
||||
}
|
||||
closeConnection() {
|
||||
this.Connection.end();
|
||||
};
|
||||
|
||||
query(queryString: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.Connection.query(queryString, (err, results) => {
|
||||
if (err) {
|
||||
this.Logger.error(`Error executing query: ${err}`);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(results);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
execute(queryString: string, values: Array<string | boolean | Date | number>): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.Connection.execute(queryString, values, (err: mysql.QueryError | null, results: mysql.QueryResult) => {
|
||||
if (err) {
|
||||
this.Logger.error(`Error executing query: ${err}`);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(results);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unprepare a previously prepared SQL query.
|
||||
*
|
||||
* @param {string} queryString
|
||||
* - The SQL query string to unprepare.
|
||||
* @return {Promise}
|
||||
* - A promise that resolves if the unprepare operation is successful,
|
||||
* or rejects with an error if there was an error unpreparing the query.
|
||||
*/
|
||||
unprepare(queryString: string): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(this.Connection.unprepare(queryString));
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const MySqlService = {
|
||||
Handler : MySqlHandler,
|
||||
User: {
|
||||
insert(handler: MySqlHandler, userData: DbUserData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _now = new Date()
|
||||
const _sql = "INSERT INTO `users`(`username`, `displayName`, `firstName`, `lastName`, `email`, `passwordHash`, `isAdmin`, `isDisabled`, `dob`, `gdpr`, `iat`, `uat`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
const _values = [
|
||||
userData.username,
|
||||
userData.displayName,
|
||||
userData.firstName,
|
||||
userData.lastName,
|
||||
userData.email,
|
||||
userData.passwordHash,
|
||||
userData.isAdmin,
|
||||
userData.isDisabled,
|
||||
userData.dob,
|
||||
userData.gdpr,
|
||||
_now,
|
||||
_now
|
||||
]
|
||||
try {
|
||||
resolve(handler.execute(_sql, _values))
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
update(handler: MySqlHandler, userData: DbUserData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
//@ts-ignore
|
||||
const _t = `
|
||||
${userData.username ? "`username` = ?," : null}
|
||||
${userData.displayName ? "`displayName` = ?," : null}
|
||||
${userData.firstName ? "`firstName` = ?," : null}`
|
||||
|
||||
|
||||
|
||||
|
||||
const __sql = "UPDATE `users` SET `lastName` = ?, `email` = ?, `passwordHash` = ?, `isAdmin` = ?, `isDisabled` = ?, `dob` = ?, `gdpr` = ? WHERE `id` = ?";
|
||||
const __values = [
|
||||
userData.username,
|
||||
userData.displayName,
|
||||
userData.firstName,
|
||||
userData.lastName,
|
||||
userData.email,
|
||||
userData.passwordHash,
|
||||
userData.isAdmin,
|
||||
userData.isDisabled,
|
||||
userData.dob,
|
||||
userData.gdpr
|
||||
];
|
||||
try {
|
||||
resolve(handler.execute(__sql, __values));
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getById(handler: MySqlHandler, userId: string): Promise<DbUserData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "SELECT * FROM `users` WHERE `id` = ?";
|
||||
const _values = [userId];
|
||||
try {
|
||||
resolve(handler.execute(_sql, _values) as unknown as DbUserData);
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getAll(handler: MySqlHandler): Promise<Array<DbUserData>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "SELECT * FROM `users`";
|
||||
try {
|
||||
return resolve(handler.query(_sql) as unknown as Array<DbUserData>);
|
||||
} catch (err: unknown) {
|
||||
return reject();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getByUsername(handler: MySqlHandler, username: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "SELECT * FROM `users` WHERE `username` = ?";
|
||||
const _values = [username];
|
||||
try {
|
||||
resolve(handler.execute(_sql, _values));
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getByEmail(handler: MySqlHandler, email: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "SELECT * FROM `users` WHERE `email` = ?";
|
||||
const _values = [email];
|
||||
try {
|
||||
resolve(handler.execute(_sql, _values));
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getByDisplayName(handler: MySqlHandler, displayName: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "SELECT * FROM `users` WHERE `displayName` = ?";
|
||||
const _values = [displayName];
|
||||
try {
|
||||
resolve(handler.execute(_sql, _values));
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getAdminStateForId(handler: MySqlHandler, userId: string) : Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "SELECT `isAdmin` FROM `users` WHERE `id` = ?";
|
||||
const _values = [userId];
|
||||
try {
|
||||
const isAdmin = handler.execute(_sql, _values)
|
||||
isAdmin.then((result) => {
|
||||
if (result !== true) return resolve(false);
|
||||
return resolve(true)
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
delete(handler: MySqlHandler, userId: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const _sql = "DELETE FROM `users` WHERE `id` = ?";
|
||||
const _values = [userId];
|
||||
try {
|
||||
resolve(handler.execute(_sql, _values));
|
||||
} catch (err: unknown) {
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default MySqlService
|
58
tsconfig.json
Normal file
58
tsconfig.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "es6",
|
||||
"rootDir": "./src",
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@services/*": [
|
||||
"src/services/*"
|
||||
],
|
||||
"@controllers/*": [
|
||||
"src/controllers/*"
|
||||
],
|
||||
"@routes/*": [
|
||||
"src/routes/*"
|
||||
],
|
||||
"@utils/*": [
|
||||
"src/utils/*"
|
||||
],
|
||||
"@interfaces/*": [
|
||||
"src/interfaces/*"
|
||||
],
|
||||
"@validators/*": [
|
||||
"src/validators/*"
|
||||
]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"removeComments": false,
|
||||
"noEmitOnError": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"allowUnusedLabels": true,
|
||||
"allowUnreachableCode": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user