Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0 #33

Merged
merged 37 commits into from
Oct 21, 2024
Merged

V0 #33

Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ab5dcc1
feat: check user roles
jean-michelet Sep 21, 2024
8cf0a81
fix: conflicts
jean-michelet Sep 21, 2024
23cb294
refactor: nit
jean-michelet Sep 21, 2024
5b04aef
test: ensure and admin can assign and unassign a task
jean-michelet Sep 21, 2024
70f95be
fix: authorization plugin has no dependency
jean-michelet Sep 21, 2024
992977f
fix: update migrations dir path
jean-michelet Sep 21, 2024
cf526c0
fix: eslint
jean-michelet Sep 21, 2024
6087f51
refactor: nit
jean-michelet Sep 21, 2024
9c4c09e
Update .env.example
jean-michelet Sep 21, 2024
c08ffdd
refactor: use knex
jean-michelet Sep 28, 2024
48a5ef9
Merge branch 'authorization' of github.com:jean-michelet/demo into au…
jean-michelet Sep 28, 2024
c69e826
refactor: migrations
jean-michelet Oct 9, 2024
edd2b48
fix: remove useless c8 ignore comments
jean-michelet Oct 9, 2024
7483692
docs: update path
jean-michelet Oct 9, 2024
43eb97f
refactor: change JWT auth for cookie session auth
jean-michelet Oct 13, 2024
0f17f3f
chore: ci - env must have required property 'COOKIE_NAME'
jean-michelet Oct 13, 2024
e2f934b
fix: uncomment unauthenticated test
jean-michelet Oct 13, 2024
667f132
refactor: leverage fastify sensible decorators
jean-michelet Oct 13, 2024
745875c
chore: use tsx
jean-michelet Oct 16, 2024
99b362e
feat: add pagination to tasks
jean-michelet Oct 16, 2024
da0fb0d
refactor: use COUNT(*) OVER() AS rowNum for tasks pagination
jean-michelet Oct 18, 2024
8292859
refactor: decorate request for authorization
jean-michelet Oct 18, 2024
d4c335e
fix: use transaction for login controller
jean-michelet Oct 19, 2024
6240960
refactor: register cookie plugin in session plugin
jean-michelet Oct 19, 2024
188cc60
fix: conflict
jean-michelet Oct 19, 2024
ebf639f
test: mock app.compare implementation instead of reassignation
jean-michelet Oct 19, 2024
8723ee3
test: spy logger to ensure 500 error is due to Transaction failure
jean-michelet Oct 19, 2024
59d083e
feat: allow to upload task image
jean-michelet Oct 19, 2024
2ba800f
refactor: improve scripts typing
jean-michelet Oct 19, 2024
ed78e1c
docs: static and multipart plugin
jean-michelet Oct 19, 2024
b2e502c
chore: dangerous DB operations should be explicitly authorized
jean-michelet Oct 19, 2024
bfd7380
refactor: use node test runner utitities
jean-michelet Oct 19, 2024
e946939
refactor: check file size before mime-type
jean-michelet Oct 20, 2024
265940e
fix: identifier typo
jean-michelet Oct 20, 2024
7c4dbc3
feat: do not use rm -rf
jean-michelet Oct 21, 2024
5af3cd0
fix: storage path disclosure
jean-michelet Oct 21, 2024
444000e
fix: nit
jean-michelet Oct 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ FASTIFY_CLOSE_GRACE_DELAY=1000
LOG_LEVEL=info

# Security
JWT_SECRET=
COOKIE_SECRET=
COOKIE_NAME=
RATE_LIMIT_MAX=
11 changes: 6 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
paths-ignore:
- "docs/**"
- "*.md"
- "*.example"
pull_request:
paths-ignore:
- "docs/**"
Expand Down Expand Up @@ -50,11 +51,10 @@ jobs:
- name: Lint Code
run: npm run lint

- name: Generate JWT Secret
id: gen-jwt
- name: Generate COOKIE Secret
run: |
JWT_SECRET=$(openssl rand -hex 32)
echo "JWT_SECRET=$JWT_SECRET" >> $GITHUB_ENV
COOKIE_SECRET=$(openssl rand -hex 32)
echo "COOKIE_SECRET=$COOKIE_SECRET" >> $GITHUB_ENV

- name: Generate dummy .env for scripts using -env-file=.env flag
run: touch .env
Expand All @@ -66,6 +66,7 @@ jobs:
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_password
# JWT_SECRET is dynamically generated and loaded from the environment
# COOKIE_SECRET is dynamically generated and loaded from the environment
COOKIE_NAME: 'sessid'
RATE_LIMIT_MAX: 4
run: npm run db:migrate && npm run test
7 changes: 0 additions & 7 deletions @types/fastify/fastify.d.ts

This file was deleted.

14 changes: 10 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ services:
db:
image: mysql:8.4
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- 3306:3306
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 3
volumes:
- db_data:/var/lib/mysql

volumes:
db_data:
4 changes: 4 additions & 0 deletions migrations/004.do.roles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE roles (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
1 change: 1 addition & 0 deletions migrations/004.undo.roles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS roles;
7 changes: 7 additions & 0 deletions migrations/005.do.user_roles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE user_roles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
1 change: 1 addition & 0 deletions migrations/005.undo.user_roles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS user_roles;
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,31 @@
},
"scripts": {
"start": "npm run build && fastify start -l info dist/app.js",
"build": "tsc",
"build": "rm -rf ./dist && tsc",
Copy link
Member

@Fdawgs Fdawgs Oct 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rm -rf won't work for Windows users using cmd or PowerShell.
You may need to either add a script to scripts/ that cleans up dist using node:fs's rm and call that, or explicitly state in documentation for users to use Git Bash which comes with Git for Windows.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just don't use windows 🤣🤭

"watch": "tsc -w",
"dev": "npm run build && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch\" \"npm:dev:start\"",
"dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js",
"dev:start": "npm run build && fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js",
"test": "npm run db:seed && tap --jobs=1 test/**/*",
"standalone": "node --env-file=.env dist/server.js",
"standalone": "npm run build && node --env-file=.env dist/server.js",
"lint": "eslint --ignore-pattern=dist",
"lint:fix": "npm run lint -- --fix",
"db:migrate": "node --env-file=.env scripts/migrate.js",
"db:seed": "node --env-file=.env scripts/seed-database.js"
"db:create": "tsx --env-file=.env ./scripts/create-database.ts",
"db:drop": "tsx --env-file=.env ./scripts/drop-database.ts",
"db:migrate": "tsx --env-file=.env ./scripts/migrate.ts",
"db:seed": "tsx --env-file=.env ./scripts/seed-database.ts"
},
"keywords": [],
"author": "Michelet Jean <[email protected]>",
"license": "MIT",
"dependencies": {
"@fastify/autoload": "^6.0.0",
"@fastify/cookie": "^11.0.1",
"@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/session": "^11.0.1",
"@fastify/swagger": "^9.0.0",
"@fastify/swagger-ui": "^5.0.1",
"@fastify/type-provider-typebox": "^5.0.0",
Expand All @@ -41,15 +43,17 @@
"fastify": "^5.0.0",
"fastify-cli": "^7.0.0",
"fastify-plugin": "^5.0.1",
"knex": "^3.1.0",
"mysql2": "^3.11.3",
"postgrator": "^7.3.0"
},
"devDependencies": {
"@types/node": "^22.5.5",
"eslint": "^9.11.0",
"fastify-tsconfig": "^2.0.0",
"mysql2": "^3.11.3",
"neostandard": "^0.11.5",
"tap": "^21.0.1",
"tsx": "^4.19.1",
"typescript": "~5.6.2"
}
}
26 changes: 26 additions & 0 deletions scripts/create-database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createConnection, Connection } from 'mysql2/promise'

async function createDatabase () {
const connection: Connection = await createConnection({
jean-michelet marked this conversation as resolved.
Show resolved Hide resolved
host: process.env.MYSQL_HOST,
port: Number(process.env.MYSQL_PORT),
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD
})

try {
await createDB(connection)
console.log(`Database ${process.env.MYSQL_DATABASE} has been created successfully.`)
} catch (error) {
console.error('Error creating database:', error)
} finally {
await connection.end()
}
}

async function createDB (connection: Connection) {
await connection.query(`CREATE DATABASE IF NOT EXISTS \`${process.env.MYSQL_DATABASE}\``)
console.log(`Database ${process.env.MYSQL_DATABASE} created or already exists.`)
}

createDatabase()
26 changes: 26 additions & 0 deletions scripts/drop-database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createConnection, Connection } from 'mysql2/promise'

async function dropDatabase () {
const connection: Connection = await createConnection({
host: process.env.MYSQL_HOST,
port: Number(process.env.MYSQL_PORT),
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD
})

try {
await dropDB(connection)
console.log(`Database ${process.env.MYSQL_DATABASE} has been dropped successfully.`)
} catch (error) {
console.error('Error dropping database:', error)
} finally {
await connection.end()
}
}

async function dropDB (connection: Connection) {
await connection.query(`DROP DATABASE IF EXISTS \`${process.env.MYSQL_DATABASE}\``)
console.log(`Database ${process.env.MYSQL_DATABASE} dropped.`)
}

dropDatabase()
39 changes: 0 additions & 39 deletions scripts/migrate.js

This file was deleted.

61 changes: 61 additions & 0 deletions scripts/migrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import mysql, { Connection } from 'mysql2/promise'
jean-michelet marked this conversation as resolved.
Show resolved Hide resolved
import path from 'node:path'
import fs from 'node:fs'
import Postgrator from 'postgrator'

interface PostgratorResult {
rows: any;
fields: any;
}
jean-michelet marked this conversation as resolved.
Show resolved Hide resolved

async function doMigration (): Promise<void> {
const connection: Connection = await mysql.createConnection({
multipleStatements: true,
host: process.env.MYSQL_HOST,
port: Number(process.env.MYSQL_PORT),
database: process.env.MYSQL_DATABASE,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD
})

try {
const migrationDir = path.join(import.meta.dirname, '../migrations')

if (!fs.existsSync(migrationDir)) {
throw new Error(
`Migration directory "${migrationDir}" does not exist. Skipping migrations.`
)
}

const postgrator = new Postgrator({
migrationPattern: path.join(migrationDir, '*'),
driver: 'mysql',
database: process.env.MYSQL_DATABASE,
execQuery: async (query: string): Promise<PostgratorResult> => {
const [rows, fields] = await connection.query(query)
return { rows, fields }
},
schemaTable: 'schemaversion'
})

await postgrator.migrate()

console.log('Migration completed!')

await new Promise<void>((resolve, reject) => {
jean-michelet marked this conversation as resolved.
Show resolved Hide resolved
connection.end((err: unknown) => {
if (err) {
return reject(err)
}

resolve()
})
})
} catch (error) {
console.error(error)
} finally {
await connection.end()
}
}

doMigration()
60 changes: 0 additions & 60 deletions scripts/seed-database.js

This file was deleted.

Loading