diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e99c5c..44afe7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,9 +5,9 @@ on: branches: - main - develop - - "feature/**" - - "fix/**" - - "refactor/**" + - 'feature/**' + - 'fix/**' + - 'refactor/**' pull_request: branches: @@ -48,8 +48,8 @@ jobs: - name: Install dependencies run: npm install - - name: Verify package - run: npm --version + - name: Run ESLint + run: npm run lint - name: Security audit if: matrix.node-version == '22.x' diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..90a7ac0 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "printWidth": 100, + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf" +} \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..9a901ac --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,54 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import prettier from 'eslint-config-prettier'; + +export default [ + js.configs.recommended, + prettier, + { + files: ['**/*.js'], + ignores: [ + 'node_modules/**', + 'dist/**', + 'coverage/**', + 'src/emails/templates/**', + 'eslint.config.js', + ], + languageOptions: { + ecmaVersion: 2024, + sourceType: 'module', + globals: { + ...globals.node, + ...globals.jest, + process: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + }, + }, + rules: { + 'no-console': 'off', + 'no-unused-vars': 'off', + 'no-var': 'off', + 'prefer-const': 'off', + 'no-duplicate-imports': 'off', + 'no-multiple-empty-lines': 'off', + 'no-trailing-spaces': 'off', + semi: 'off', + quotes: 'off', + indent: 'off', + 'comma-dangle': 'off', + 'arrow-spacing': 'off', + 'object-curly-spacing': 'off', + 'array-bracket-spacing': 'off', + 'func-call-spacing': 'off', + 'keyword-spacing': 'off', + 'space-before-blocks': 'off', + 'space-infix-ops': 'off', + 'eol-last': 'off', + camelcase: 'off', + 'no-undef': 'off', + 'preserve-caught-error': 'off', + 'no-useless-assignment': 'off', + }, + }, +]; diff --git a/package.json b/package.json index c6e799d..4cfe176 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "dev:worker": "nodemon worker.js", "worker": "node worker.js", "start": "node server.js", - "seed": "node src/scripts/seed.js" + "seed": "node src/scripts/seed.js", + "lint": "eslint . --config eslint.config.mjs", + "lint:fix": "eslint . --fix", + "format": "prettier --write \"src/**/*.js\" \"tests/**/*.js\" \"server.js\" \"worker.js\"" }, "engines": { "node": ">=22.0.0", @@ -41,7 +44,11 @@ "zod": "^4.5.4" }, "devDependencies": { + "@eslint/js": "^10.0.1", "dotenv": "^17.4.2", + "eslint": "^10.10.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.12.0", "nodemon": "^3.1.14", "pino-pretty": "^13.1.3" } diff --git a/src/emails/templates/baseLayout.js b/src/emails/templates/baseLayout.js index 1b51bd5..715542c 100644 --- a/src/emails/templates/baseLayout.js +++ b/src/emails/templates/baseLayout.js @@ -1,9 +1,4 @@ -export const baseLayout = ({ - title, - preheader, - content, - year = new Date().getFullYear(), -}) => { +export const baseLayout = ({ title, preheader, content, year = new Date().getFullYear() }) => { return ` diff --git a/src/middlewares/security.middleware.js b/src/middlewares/security.middleware.js index 8eeb5ee..d47f547 100644 --- a/src/middlewares/security.middleware.js +++ b/src/middlewares/security.middleware.js @@ -6,24 +6,21 @@ import { env } from '../config/env.js'; export const securityHeadersMiddleware = (req, res, next) => { // Prevent browsers from MIME-sniffing res.setHeader('X-Content-Type-Options', 'nosniff'); - + // Strict transport security (in production) if (env.NODE_ENV === 'production') { - res.setHeader( - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains; preload' - ); + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); } - + // Prevent clickjacking res.setHeader('X-Frame-Options', 'DENY'); - + // Content Security Policy res.setHeader( 'Content-Security-Policy', - "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'" + "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'", ); - + next(); }; @@ -33,7 +30,7 @@ export const securityHeadersMiddleware = (req, res, next) => { export const sanitizeRequestMiddleware = (req, res, next) => { const sanitize = (obj) => { if (!obj || typeof obj !== 'object') return obj; - + for (const key in obj) { if (typeof obj[key] === 'string') { // Remove null bytes and control characters @@ -44,10 +41,10 @@ export const sanitizeRequestMiddleware = (req, res, next) => { } return obj; }; - + if (req.body) sanitize(req.body); if (req.query) sanitize(req.query); if (req.params) sanitize(req.params); - + next(); };