diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e3f676..617fe18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,16 +5,13 @@ on: branches: [ "main", "develop" ] pull_request: branches: [ "main", "develop" ] + workflow_dispatch: jobs: unittcms-test: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.x] - steps: - uses: actions/checkout@v4 @@ -26,9 +23,11 @@ jobs: cache: 'npm' - name: Install dependencies run: npm ci - - name: Format code - run: npm run format - - name: Run unit tests + - name: Format check + run: npm run format:check + - name: ESLint check + run: npm run lint + - name: Unit tests run: npm run test # frontend diff --git a/backend/config/config.js b/backend/config/config.js index d514651..962fb95 100644 --- a/backend/config/config.js +++ b/backend/config/config.js @@ -1,5 +1,4 @@ const path = require('path'); - const databasePath = path.resolve(__dirname, '../database/database.sqlite'); module.exports = { diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 04b5e8d..daf6e53 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -1,6 +1,6 @@ const jwt = require('jsonwebtoken'); -const { roles, defaultDangerKey } = require('../routes/users/authSettings'); const { DataTypes } = require('sequelize'); +const { roles, defaultDangerKey } = require('../routes/users/authSettings'); const defineUser = require('../models/users'); function authMiddleware(sequelize) { @@ -23,6 +23,7 @@ function authMiddleware(sequelize) { req.userId = decoded.userId; next(); } catch (error) { + console.error('Token verification failed:', error); res.status(401).json({ error: 'Invalid token' }); } } diff --git a/backend/middleware/verifyEditable.js b/backend/middleware/verifyEditable.js index 9e79921..af6120b 100644 --- a/backend/middleware/verifyEditable.js +++ b/backend/middleware/verifyEditable.js @@ -1,5 +1,5 @@ -const { memberRoles } = require('../routes/users/authSettings'); const { DataTypes } = require('sequelize'); +const { memberRoles } = require('../routes/users/authSettings'); const defineMember = require('../models/members'); const defineProject = require('../models/projects'); const defineFolder = require('../models/folders'); diff --git a/backend/migrations/20240201220834-create-users.js b/backend/migrations/20240201220834-create-users.js index 539bade..04e7d06 100644 --- a/backend/migrations/20240201220834-create-users.js +++ b/backend/migrations/20240201220834-create-users.js @@ -40,7 +40,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('users'); }, }; diff --git a/backend/migrations/20240204090345-create-projects.js b/backend/migrations/20240204090345-create-projects.js index 4fbf7a9..1603d3a 100644 --- a/backend/migrations/20240204090345-create-projects.js +++ b/backend/migrations/20240204090345-create-projects.js @@ -42,7 +42,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('projects'); }, }; diff --git a/backend/migrations/20240212022145-create-folders.js b/backend/migrations/20240212022145-create-folders.js index 01f60dc..64806ab 100644 --- a/backend/migrations/20240212022145-create-folders.js +++ b/backend/migrations/20240212022145-create-folders.js @@ -42,7 +42,7 @@ module.exports = { }); }, - async down(queryInterface, Sequelize) { + async down(queryInterface) { await queryInterface.dropTable('folders'); }, }; diff --git a/backend/migrations/20240212082050-create-runs.js b/backend/migrations/20240212082050-create-runs.js index dad3e1d..55e9fc0 100644 --- a/backend/migrations/20240212082050-create-runs.js +++ b/backend/migrations/20240212082050-create-runs.js @@ -46,7 +46,7 @@ module.exports = { }); }, - async down(queryInterface, Sequelize) { + async down(queryInterface) { await queryInterface.dropTable('runs'); }, }; diff --git a/backend/migrations/20240212084228-create-cases.js b/backend/migrations/20240212084228-create-cases.js index 5564161..4ae2213 100644 --- a/backend/migrations/20240212084228-create-cases.js +++ b/backend/migrations/20240212084228-create-cases.js @@ -66,7 +66,7 @@ module.exports = { }); }, - async down(queryInterface, Sequelize) { + async down(queryInterface) { await queryInterface.dropTable('cases'); }, }; diff --git a/backend/migrations/20240229095914-create-steps.js b/backend/migrations/20240229095914-create-steps.js index 5cb30e8..9662b15 100644 --- a/backend/migrations/20240229095914-create-steps.js +++ b/backend/migrations/20240229095914-create-steps.js @@ -28,7 +28,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('steps'); }, }; diff --git a/backend/migrations/20240229100158-create-cases-steps.js b/backend/migrations/20240229100158-create-cases-steps.js index 28c17f9..ff66e7e 100644 --- a/backend/migrations/20240229100158-create-cases-steps.js +++ b/backend/migrations/20240229100158-create-cases-steps.js @@ -46,7 +46,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('caseSteps'); }, }; diff --git a/backend/migrations/20240316200815-create-attachments.js b/backend/migrations/20240316200815-create-attachments.js index cbd51c0..bc2044e 100644 --- a/backend/migrations/20240316200815-create-attachments.js +++ b/backend/migrations/20240316200815-create-attachments.js @@ -32,7 +32,7 @@ module.exports = { }); }, - async down(queryInterface, Sequelize) { + async down(queryInterface) { await queryInterface.dropTable('attachments'); }, }; diff --git a/backend/migrations/20240316201158-create-cases-attachments.js b/backend/migrations/20240316201158-create-cases-attachments.js index edd4812..d7712c4 100644 --- a/backend/migrations/20240316201158-create-cases-attachments.js +++ b/backend/migrations/20240316201158-create-cases-attachments.js @@ -42,7 +42,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('caseAttachments'); }, }; diff --git a/backend/migrations/20240407182431-create-runs-cases.js b/backend/migrations/20240407182431-create-runs-cases.js index 536fd47..dbc2d22 100644 --- a/backend/migrations/20240407182431-create-runs-cases.js +++ b/backend/migrations/20240407182431-create-runs-cases.js @@ -46,7 +46,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('runCases'); }, }; diff --git a/backend/migrations/20240529221509-create-members.js b/backend/migrations/20240529221509-create-members.js index a28aff0..ed7576a 100644 --- a/backend/migrations/20240529221509-create-members.js +++ b/backend/migrations/20240529221509-create-members.js @@ -46,7 +46,7 @@ module.exports = { }); }, - down: async (queryInterface, Sequelize) => { + down: async (queryInterface) => { await queryInterface.dropTable('members'); }, }; diff --git a/backend/migrations/20240930124156-rename_table.js b/backend/migrations/20240930124156-rename_table.js index 2ba10b5..c8e572d 100644 --- a/backend/migrations/20240930124156-rename_table.js +++ b/backend/migrations/20240930124156-rename_table.js @@ -2,7 +2,7 @@ /** @type {import('sequelize-cli').Migration} */ module.exports = { - async up(queryInterface, Sequelize) { + async up(queryInterface) { /** * Add altering commands here. * @@ -15,7 +15,7 @@ module.exports = { await queryInterface.renameColumn('users', 'avatarPath', 'avatar_path'); }, - async down(queryInterface, Sequelize) { + async down() { /** * Add reverting commands here. * diff --git a/backend/models/index.js b/backend/models/index.js index e42d923..e57f031 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -2,8 +2,8 @@ const fs = require('fs'); const path = require('path'); -const Sequelize = require('sequelize'); const process = require('process'); +const Sequelize = require('sequelize'); const basename = path.basename(__filename); const env = process.env.NODE_ENV || 'development'; const config = require(__dirname + '/../config/config.json')[env]; diff --git a/backend/routes/cases/delete.js b/backend/routes/cases/delete.js index 8f6a4f5..62e880d 100644 --- a/backend/routes/cases/delete.js +++ b/backend/routes/cases/delete.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineCase = require('../../models/cases'); const { DataTypes } = require('sequelize'); +const defineCase = require('../../models/cases'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/cases/edit.js b/backend/routes/cases/edit.js index 6154445..bb5848c 100644 --- a/backend/routes/cases/edit.js +++ b/backend/routes/cases/edit.js @@ -1,14 +1,12 @@ const express = require('express'); const router = express.Router(); -const defineCase = require('../../models/cases'); -const defineStep = require('../../models/steps'); const { DataTypes } = require('sequelize'); +const defineCase = require('../../models/cases'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); const { verifyProjectDeveloperFromCaseId } = require('../../middleware/verifyEditable')(sequelize); const Case = defineCase(sequelize, DataTypes); - const Step = defineStep(sequelize, DataTypes); router.put('/:caseId', verifySignedIn, verifyProjectDeveloperFromCaseId, async (req, res) => { const caseId = req.params.caseId; diff --git a/backend/routes/cases/index.js b/backend/routes/cases/index.js index d2b0666..170cf1f 100644 --- a/backend/routes/cases/index.js +++ b/backend/routes/cases/index.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineCase = require('../../models/cases'); const { DataTypes } = require('sequelize'); +const defineCase = require('../../models/cases'); module.exports = function (sequelize) { const Case = defineCase(sequelize, DataTypes); diff --git a/backend/routes/cases/indexByProjectId.js b/backend/routes/cases/indexByProjectId.js index 35c56ec..4c99002 100644 --- a/backend/routes/cases/indexByProjectId.js +++ b/backend/routes/cases/indexByProjectId.js @@ -1,10 +1,10 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineProject = require('../../models/projects'); const defineFolder = require('../../models/folders'); const defineCase = require('../../models/cases'); const defineRunCase = require('../../models/runCases'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const Project = defineProject(sequelize, DataTypes); diff --git a/backend/routes/cases/new.js b/backend/routes/cases/new.js index 936b0c4..958f36f 100644 --- a/backend/routes/cases/new.js +++ b/backend/routes/cases/new.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineCase = require('../../models/cases'); const { DataTypes } = require('sequelize'); +const defineCase = require('../../models/cases'); const requiredFields = ['title', 'state', 'priority', 'type', 'automationStatus', 'template']; @@ -50,6 +50,7 @@ module.exports = function (sequelize) { res.json(newCase); } catch (error) { + console.error('Error creating new case:', error); res.status(500).json({ error: 'Internal server error' }); } }); diff --git a/backend/routes/cases/show.js b/backend/routes/cases/show.js index 4b936ad..a54ee05 100644 --- a/backend/routes/cases/show.js +++ b/backend/routes/cases/show.js @@ -1,9 +1,9 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineCase = require('../../models/cases'); const defineStep = require('../../models/steps'); const defineAttachment = require('../../models/attachments'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const Case = defineCase(sequelize, DataTypes); diff --git a/backend/routes/folders/delete.js b/backend/routes/folders/delete.js index 0085313..2eeb061 100644 --- a/backend/routes/folders/delete.js +++ b/backend/routes/folders/delete.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineFolder = require('../../models/folders'); const { DataTypes } = require('sequelize'); +const defineFolder = require('../../models/folders'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/folders/edit.js b/backend/routes/folders/edit.js index 8c2be86..d0472f2 100644 --- a/backend/routes/folders/edit.js +++ b/backend/routes/folders/edit.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineFolder = require('../../models/folders'); const { DataTypes } = require('sequelize'); +const defineFolder = require('../../models/folders'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/folders/index.js b/backend/routes/folders/index.js index 2c34bee..611796e 100644 --- a/backend/routes/folders/index.js +++ b/backend/routes/folders/index.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineFolder = require('../../models/folders'); const { DataTypes } = require('sequelize'); +const defineFolder = require('../../models/folders'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/folders/new.js b/backend/routes/folders/new.js index e068000..f184556 100644 --- a/backend/routes/folders/new.js +++ b/backend/routes/folders/new.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineFolder = require('../../models/folders'); const { DataTypes } = require('sequelize'); +const defineFolder = require('../../models/folders'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); @@ -25,6 +25,7 @@ module.exports = function (sequelize) { res.json(newFolder); } catch (error) { + console.error('Error creating new folder:', error); res.status(500).json({ error: 'Internal server error' }); } }); diff --git a/backend/routes/home/index.js b/backend/routes/home/index.js index a410a42..292d81e 100644 --- a/backend/routes/home/index.js +++ b/backend/routes/home/index.js @@ -1,11 +1,11 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineProject = require('../../models/projects'); const defineFolder = require('../../models/folders'); const defineCase = require('../../models/cases'); const defineRun = require('../../models/runs'); const defineRunCase = require('../../models/runCases'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/members/check.js b/backend/routes/members/check.js index 598dd0c..f68ddd1 100644 --- a/backend/routes/members/check.js +++ b/backend/routes/members/check.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineMember = require('../../models/members'); const defineProject = require('../../models/projects'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/members/delete.js b/backend/routes/members/delete.js index ec0208f..dc02283 100644 --- a/backend/routes/members/delete.js +++ b/backend/routes/members/delete.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineMember = require('../../models/members'); const { DataTypes } = require('sequelize'); +const defineMember = require('../../models/members'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/members/edit.js b/backend/routes/members/edit.js index c975270..6c7c64e 100644 --- a/backend/routes/members/edit.js +++ b/backend/routes/members/edit.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineMember = require('../../models/members'); const { DataTypes } = require('sequelize'); +const defineMember = require('../../models/members'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/members/index.js b/backend/routes/members/index.js index 902059b..415ebc1 100644 --- a/backend/routes/members/index.js +++ b/backend/routes/members/index.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineUser = require('../../models/users'); const defineMember = require('../../models/members'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/members/new.js b/backend/routes/members/new.js index ade8aab..f82ca44 100644 --- a/backend/routes/members/new.js +++ b/backend/routes/members/new.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineMember = require('../../models/members'); const { DataTypes } = require('sequelize'); +const defineMember = require('../../models/members'); const { memberRoles } = require('../../routes/users/authSettings'); module.exports = function (sequelize) { diff --git a/backend/routes/projects/delete.js b/backend/routes/projects/delete.js index 47dadc6..40a1ab3 100644 --- a/backend/routes/projects/delete.js +++ b/backend/routes/projects/delete.js @@ -1,9 +1,9 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineProject = require('../../models/projects'); const defineFolder = require('../../models/folders'); const defineRun = require('../../models/runs'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/projects/edit.js b/backend/routes/projects/edit.js index a90c65b..1130999 100644 --- a/backend/routes/projects/edit.js +++ b/backend/routes/projects/edit.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineProject = require('../../models/projects'); const { DataTypes } = require('sequelize'); +const defineProject = require('../../models/projects'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/projects/new.js b/backend/routes/projects/new.js index 74296fc..247ef54 100644 --- a/backend/routes/projects/new.js +++ b/backend/routes/projects/new.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineProject = require('../../models/projects'); const { DataTypes } = require('sequelize'); +const defineProject = require('../../models/projects'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/projects/show.js b/backend/routes/projects/show.js index 8594577..d83bf01 100644 --- a/backend/routes/projects/show.js +++ b/backend/routes/projects/show.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineProject = require('../../models/projects'); const defineFolder = require('../../models/folders'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/runcases/edit.js b/backend/routes/runcases/edit.js index 6819418..962f42d 100644 --- a/backend/routes/runcases/edit.js +++ b/backend/routes/runcases/edit.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineRunCase = require('../../models/runCases'); const { DataTypes } = require('sequelize'); +const defineRunCase = require('../../models/runCases'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/runcases/index.js b/backend/routes/runcases/index.js index ba1dacc..7b17d2c 100644 --- a/backend/routes/runcases/index.js +++ b/backend/routes/runcases/index.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineRunCase = require('../../models/runCases'); const { DataTypes } = require('sequelize'); +const defineRunCase = require('../../models/runCases'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/runs/delete.js b/backend/routes/runs/delete.js index 3b32069..2bde53c 100644 --- a/backend/routes/runs/delete.js +++ b/backend/routes/runs/delete.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineRun = require('../../models/runs'); const { DataTypes } = require('sequelize'); +const defineRun = require('../../models/runs'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/runs/edit.js b/backend/routes/runs/edit.js index e921c3b..5f05b80 100644 --- a/backend/routes/runs/edit.js +++ b/backend/routes/runs/edit.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineRun = require('../../models/runs'); const { DataTypes } = require('sequelize'); +const defineRun = require('../../models/runs'); module.exports = function (sequelize) { const Run = defineRun(sequelize, DataTypes); diff --git a/backend/routes/runs/index.js b/backend/routes/runs/index.js index c77959c..1314912 100644 --- a/backend/routes/runs/index.js +++ b/backend/routes/runs/index.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineRun = require('../../models/runs'); const { DataTypes } = require('sequelize'); +const defineRun = require('../../models/runs'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/runs/new.js b/backend/routes/runs/new.js index 7c6655c..72731d0 100644 --- a/backend/routes/runs/new.js +++ b/backend/routes/runs/new.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineRun = require('../../models/runs'); const { DataTypes } = require('sequelize'); +const defineRun = require('../../models/runs'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); @@ -26,6 +26,7 @@ module.exports = function (sequelize) { res.json(newRun); } catch (error) { + console.error('Error creating new run:', error); res.status(500).json({ error: 'Internal server error' }); } }); diff --git a/backend/routes/runs/show.js b/backend/routes/runs/show.js index 2bf415c..c84b9e6 100644 --- a/backend/routes/runs/show.js +++ b/backend/routes/runs/show.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); +const { DataTypes, literal } = require('sequelize'); const defineRun = require('../../models/runs'); const defineRunCase = require('../../models/runCases'); -const { DataTypes, literal } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/steps/edit.js b/backend/routes/steps/edit.js index aa5a7a7..2fac1f7 100644 --- a/backend/routes/steps/edit.js +++ b/backend/routes/steps/edit.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); +const { DataTypes } = require('sequelize'); const defineStep = require('../../models/steps'); const defineCaseStep = require('../../models/caseSteps'); -const { DataTypes } = require('sequelize'); module.exports = function (sequelize) { const Step = defineStep(sequelize, DataTypes); diff --git a/backend/routes/users/find.js b/backend/routes/users/find.js index f404455..07897c2 100644 --- a/backend/routes/users/find.js +++ b/backend/routes/users/find.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineUser = require('../../models/users'); const { DataTypes } = require('sequelize'); +const defineUser = require('../../models/users'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/users/index.js b/backend/routes/users/index.js index 4f0fd95..fa2ab03 100644 --- a/backend/routes/users/index.js +++ b/backend/routes/users/index.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); -const defineUser = require('../../models/users'); const { DataTypes } = require('sequelize'); +const defineUser = require('../../models/users'); module.exports = function (sequelize) { const { verifySignedIn, verifyAdmin } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/users/search.js b/backend/routes/users/search.js index 84ba3f1..e8d9c84 100644 --- a/backend/routes/users/search.js +++ b/backend/routes/users/search.js @@ -1,8 +1,8 @@ const express = require('express'); const router = express.Router(); +const { DataTypes, Op } = require('sequelize'); const defineUser = require('../../models/users'); const defineMember = require('../../models/members'); -const { DataTypes, Op } = require('sequelize'); module.exports = function (sequelize) { const { verifySignedIn } = require('../../middleware/auth')(sequelize); diff --git a/backend/routes/users/signin.js b/backend/routes/users/signin.js index 3b740f4..69459b2 100644 --- a/backend/routes/users/signin.js +++ b/backend/routes/users/signin.js @@ -1,9 +1,9 @@ const express = require('express'); const router = express.Router(); -const defineUser = require('../../models/users'); const { DataTypes } = require('sequelize'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); +const defineUser = require('../../models/users'); const { defaultDangerKey } = require('./authSettings'); module.exports = function (sequelize) { diff --git a/backend/routes/users/signup.js b/backend/routes/users/signup.js index 8398f29..ee2fae1 100644 --- a/backend/routes/users/signup.js +++ b/backend/routes/users/signup.js @@ -1,10 +1,10 @@ const express = require('express'); const router = express.Router(); -const defineUser = require('../../models/users'); const { DataTypes } = require('sequelize'); -const { roles, defaultDangerKey } = require('./authSettings'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); +const defineUser = require('../../models/users'); +const { roles, defaultDangerKey } = require('./authSettings'); module.exports = function (sequelize) { const User = defineUser(sequelize, DataTypes); diff --git a/backend/routes/users/update.mjs b/backend/routes/users/update.mjs index 3c5be5a..d45f757 100644 --- a/backend/routes/users/update.mjs +++ b/backend/routes/users/update.mjs @@ -1,8 +1,8 @@ import express from 'express'; import { DataTypes } from 'sequelize'; import defineUser from '../../models/users.js'; -import { roles } from './authSettings.js'; import authMiddleware from '../../middleware/auth.js'; +import { roles } from './authSettings.js'; const router = express.Router(); export default function (sequelize) { diff --git a/backend/routes/users/update.test.js b/backend/routes/users/update.test.js index 447dfd2..1eaa514 100644 --- a/backend/routes/users/update.test.js +++ b/backend/routes/users/update.test.js @@ -1,8 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import request from 'supertest'; import express from 'express'; -import updateRoute from './update'; import { Sequelize } from 'sequelize'; +import updateRoute from './update'; import { roles } from './authSettings'; const adminRoleIndex = roles.findIndex((entry) => entry.uid === 'administrator'); @@ -28,7 +28,7 @@ const mockUser = { update: vi.fn(), }; vi.mock('../../models/users', () => ({ - default: (sequelize, DataTypes) => mockUser, + default: () => mockUser, })); // test diff --git a/backend/seeders/seed.js b/backend/seeders/seed.js index 83efa5c..674bfa3 100644 --- a/backend/seeders/seed.js +++ b/backend/seeders/seed.js @@ -3,7 +3,7 @@ const fs = require('fs'); const bcrypt = require('bcrypt'); module.exports = { - up: async (queryInterface, Sequelize) => { + up: async (queryInterface) => { const hashedPassword = await bcrypt.hash('password', 10); // Add projects table records @@ -696,7 +696,7 @@ module.exports = { ]); }, - down: async (queryInterface, Sequelize) => { - // do nothingg + down: async () => { + // do nothing }, }; diff --git a/combined_server.js b/combined_server.js index ea552a4..bfcca11 100644 --- a/combined_server.js +++ b/combined_server.js @@ -1,9 +1,9 @@ // Use express from backend node_modules -const express = require('./backend/node_modules/express'); const { createServer: createHttpServer } = require('http'); const path = require('path'); const fs = require('fs'); const { execSync } = require('child_process'); +const express = require('./backend/node_modules/express'); async function runMigrations() { try { diff --git a/docs/docs/dev/technologies.md b/docs/docs/dev/technologies.md index 8ea7434..3618db8 100644 --- a/docs/docs/dev/technologies.md +++ b/docs/docs/dev/technologies.md @@ -4,7 +4,7 @@ sidebar_position: 1 # Technologies -UnitTCMS is powered by following technologies. For more detailed package and version information, please see package.json. +UnitTCMS is powered by following technologies. For more detailed info, please check package.json. ## Frontend @@ -12,7 +12,7 @@ UnitTCMS is powered by following technologies. For more detailed package and ver | ----------------------------------------------- | -------------------- | | [React](https://react.dev/) | Rendering | | [Next.js](https://nextjs.org/) | Routing, etc. | -| [NextUI](https://nextui.org/) | UI library | +| [HeroUI](https://www.heroui.com/) | UI library | | [next-intl](https://next-intl-docs.vercel.app/) | Internationalization | | [ApexCharts](https://apexcharts.com/) | Charts | @@ -24,3 +24,12 @@ UnitTCMS is powered by following technologies. For more detailed package and ver | [Express](https://expressjs.com/) | API Server | | [Sequelize](https://sequelize.org/) | ORM | | [SQLite](https://www.sqlite.org/) | database | + +## Other + +| Technology | Use | +| ------------------------------------- | ---------------------- | +| [Prettier](https://prettier.io/) | Format | +| [ESLint](https://eslint.org/) | ESLint | +| [Vitest](https://vitest.dev/) | Unit Testing Framework | +| [Playwright](https://playwright.dev/) | E2E Testing Framework | diff --git a/frontend/components/Footer.tsx b/frontend/components/Footer.tsx index 649c096..a100da3 100644 --- a/frontend/components/Footer.tsx +++ b/frontend/components/Footer.tsx @@ -1,3 +1,4 @@ +import Image from 'next/image'; import { Link, NextUiLinkClasses } from '@/src/i18n/routing'; import { LocaleCodeType } from '@/types/locale'; @@ -7,10 +8,18 @@ type Props = { export default function Footer({ locale }: Props) { return ( -
-
Copyright © 2024-present UnitTCMS
+
+ + Logo + +
+ Copyright © 2024-present + + UnitTCMS + +
- + Status
diff --git a/frontend/components/ProjectDialog.tsx b/frontend/components/ProjectDialog.tsx index ac36642..1fe4a35 100644 --- a/frontend/components/ProjectDialog.tsx +++ b/frontend/components/ProjectDialog.tsx @@ -1,5 +1,4 @@ 'use client'; -import React from 'react'; import { useState, useEffect } from 'react'; import { Button, @@ -63,6 +62,7 @@ export default function ProjectDialog({ isOpen, editingProject, onCancel, onSubm setIsProjectPublic(true); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [editingProject]); const clear = () => { diff --git a/frontend/components/ThemeSwitch.tsx b/frontend/components/ThemeSwitch.tsx index edc0e48..dc48826 100644 --- a/frontend/components/ThemeSwitch.tsx +++ b/frontend/components/ThemeSwitch.tsx @@ -19,7 +19,11 @@ export const ThemeSwitch: FC = ({ className, classNames }) => const isSSR = useIsSSR(); const onChange = () => { - theme === 'light' ? setTheme('dark') : setTheme('light'); + if (theme === 'light') { + setTheme('dark'); + } else { + setTheme('light'); + } }; const { Component, slots, isSelected, getBaseProps, getInputProps, getWrapperProps } = useSwitch({ diff --git a/frontend/components/UserAvatar.tsx b/frontend/components/UserAvatar.tsx index efc2055..22204d9 100644 --- a/frontend/components/UserAvatar.tsx +++ b/frontend/components/UserAvatar.tsx @@ -10,7 +10,7 @@ export default function PublicityChip({ context }: Props) { return context.isSignedIn() ? ( diff --git a/frontend/src/app/[locale]/DropdownAccount.tsx b/frontend/src/app/[locale]/DropdownAccount.tsx index ec39883..23ab32a 100644 --- a/frontend/src/app/[locale]/DropdownAccount.tsx +++ b/frontend/src/app/[locale]/DropdownAccount.tsx @@ -78,7 +78,7 @@ export default function DropdownAccount({ messages, locale, onItemPress }: Props startContent={} endContent={} > - {context.isSignedIn() ? context.token!.user!.username : messages.signIn} + {context.isSignedIn() ? context.token?.user?.username : messages.signIn} {context.isSignedIn() ? ( diff --git a/frontend/src/app/[locale]/HeaderNavbarMenu.tsx b/frontend/src/app/[locale]/HeaderNavbarMenu.tsx index dc2f90b..202e55e 100644 --- a/frontend/src/app/[locale]/HeaderNavbarMenu.tsx +++ b/frontend/src/app/[locale]/HeaderNavbarMenu.tsx @@ -1,7 +1,5 @@ 'use client'; import { useState, useContext } from 'react'; -import { TokenContext } from '@/utils/TokenProvider'; -import { Link, useRouter } from '@/src/i18n/routing'; import { usePathname } from 'next/navigation'; import Image from 'next/image'; import { @@ -12,16 +10,17 @@ import { NavbarBrand, NavbarItem, Link as NextUiLink, - Chip, ListboxItem, Listbox, } from '@heroui/react'; import { ArrowRightFromLine, ArrowRightToLine, File, Globe, MoveUpRight, PenTool } from 'lucide-react'; +import DropdownAccount from './DropdownAccount'; +import DropdownLanguage from './DropdownLanguage'; import { ThemeSwitch } from '@/components/ThemeSwitch'; import { GithubIcon } from '@/components/icons'; import { locales } from '@/config/selection'; -import DropdownAccount from './DropdownAccount'; -import DropdownLanguage from './DropdownLanguage'; +import { Link, useRouter } from '@/src/i18n/routing'; +import { TokenContext } from '@/utils/TokenProvider'; import UserAvatar from '@/components/UserAvatar'; import { LocaleCodeType } from '@/types/locale'; import Config from '@/config/config'; diff --git a/frontend/src/app/[locale]/account/AccountPage.tsx b/frontend/src/app/[locale]/account/AccountPage.tsx index 6a5a5ff..9c7a693 100644 --- a/frontend/src/app/[locale]/account/AccountPage.tsx +++ b/frontend/src/app/[locale]/account/AccountPage.tsx @@ -1,14 +1,15 @@ 'use client'; import { useState, useEffect, useContext } from 'react'; -import { Link, NextUiLinkClasses } from '@/src/i18n/routing'; import { Button, Card, CardHeader, CardFooter } from '@heroui/react'; -import { TokenContext } from '@/utils/TokenProvider'; import Avatar from 'boring-avatars'; +import { ArrowRight } from 'lucide-react'; +import { Link, NextUiLinkClasses } from '@/src/i18n/routing'; +import { TokenContext } from '@/utils/TokenProvider'; import { fetchMyProjects } from '@/utils/projectsControl'; import { ProjectType } from '@/types/project'; import PublicityChip from '@/components/PublicityChip'; import { LocaleCodeType } from '@/types/locale'; -import { ArrowRight } from 'lucide-react'; +import { logError } from '@/utils/errorHandler'; type AccountPageMessages = { yourProjects: string; @@ -36,8 +37,8 @@ export default function AccountPage({ messages, locale }: Props) { try { const data = await fetchMyProjects(context.token.access_token); setMyProjects(data); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } @@ -53,13 +54,13 @@ export default function AccountPage({ messages, locale }: Props) {
-

{context.token!.user!.username}

-

{context.token!.user!.email}

+

{context.token?.user?.username}

+

{context.token?.user?.email}

diff --git a/frontend/src/app/[locale]/account/authControl.ts b/frontend/src/app/[locale]/account/authControl.ts index 530535a..f0d77df 100644 --- a/frontend/src/app/[locale]/account/authControl.ts +++ b/frontend/src/app/[locale]/account/authControl.ts @@ -1,6 +1,7 @@ import { UserType } from '@/types/user'; import Config from '@/config/config'; import { roles } from '@/config/selection'; +import { logError } from '@/utils/errorHandler'; const apiServer = Config.apiServer; async function signUp(newUser: UserType) { @@ -21,8 +22,8 @@ async function signUp(newUser: UserType) { } const token = await response.json(); return token; - } catch (error: any) { - console.error('Error sign up:', error); + } catch (error: unknown) { + logError('Error sign up:', error); throw error; } } @@ -45,8 +46,8 @@ async function signIn(signInUser: UserType) { } const token = await response.json(); return token; - } catch (error: any) { - console.error('Error sign in:', error); + } catch (error: unknown) { + logError('Error sign in:', error); throw error; } } diff --git a/frontend/src/app/[locale]/account/authPage.tsx b/frontend/src/app/[locale]/account/authPage.tsx index 34a8df5..d83cea3 100644 --- a/frontend/src/app/[locale]/account/authPage.tsx +++ b/frontend/src/app/[locale]/account/authPage.tsx @@ -1,17 +1,17 @@ 'use client'; -import React from 'react'; import { useState, useContext } from 'react'; import { Input, Button, Card, CardHeader, CardBody } from '@heroui/react'; -import { Link } from '@/src/i18n/routing'; import { ChevronRight, Eye, EyeOff } from 'lucide-react'; -import { UserType, AuthMessages } from '@/types/user'; -import { roles } from '@/config/selection'; import { signUp, signIn, signInAsGuest } from './authControl'; import { isValidEmail, isValidPassword } from './validate'; +import { Link } from '@/src/i18n/routing'; +import { UserType, AuthMessages } from '@/types/user'; +import { roles } from '@/config/selection'; import { TokenContext } from '@/utils/TokenProvider'; import { useRouter } from '@/src/i18n/routing'; import Config from '@/config/config'; import { LocaleCodeType } from '@/types/locale'; +import Footer from '@/components/Footer'; const isDemoSite = Config.isDemoSite; type Props = { @@ -94,74 +94,56 @@ export default function AuthPage({ isSignup, messages, locale }: Props) { }; return ( - - -

{messages.title}

- -
- -
- {errorMessage &&
{errorMessage}
} - { - setUser({ - ...user, - email: e.target.value, - }); - }} - /> - {isSignup && ( +
+ + +

{messages.title}

+ +
+ + + {errorMessage &&
{errorMessage}
} { setUser({ ...user, - username: e.target.value, + email: e.target.value, }); }} /> - )} - - {isPasswordVisible ? : } - - } - onChange={(e) => { - setUser({ - ...user, - password: e.target.value, - }); - }} - /> - {isSignup && ( + {isSignup && ( + { + setUser({ + ...user, + username: e.target.value, + }); + }} + /> + )} } onChange={(e) => { - setConfirmPassword(e.target.value); + setUser({ + ...user, + password: e.target.value, + }); }} /> - )} - - {isDemoSite &&
{messages.demoPageWarning}
} - -
- - {!isSignup && isDemoSite && ( - + {isSignup && ( + + {isPasswordVisible ? : } + + } + onChange={(e) => { + setConfirmPassword(e.target.value); + }} + /> )} -
- -
-
+ + {isDemoSite &&
{messages.demoPageWarning}
} + +
+ + {!isSignup && isDemoSite && ( + + )} +
+ + + +
+
); } diff --git a/frontend/src/app/[locale]/account/layout.tsx b/frontend/src/app/[locale]/account/layout.tsx index e03958b..c161c4e 100644 --- a/frontend/src/app/[locale]/account/layout.tsx +++ b/frontend/src/app/[locale]/account/layout.tsx @@ -1,5 +1,5 @@ -import { LocaleCodeType } from '@/types/locale'; import { getTranslations } from 'next-intl/server'; +import { LocaleCodeType } from '@/types/locale'; export async function generateMetadata({ params: { locale } }: { params: { locale: LocaleCodeType } }) { const t = await getTranslations({ locale, namespace: 'Auth' }); diff --git a/frontend/src/app/[locale]/account/signin/page.tsx b/frontend/src/app/[locale]/account/signin/page.tsx index 92da2f5..e5e8d06 100644 --- a/frontend/src/app/[locale]/account/signin/page.tsx +++ b/frontend/src/app/[locale]/account/signin/page.tsx @@ -1,7 +1,7 @@ -import { PageType } from '@/types/base'; -import AuthPage from '../authPage'; import { getTranslations } from 'next-intl/server'; import { useTranslations } from 'next-intl'; +import AuthPage from '../authPage'; +import { PageType } from '@/types/base'; import { LocaleCodeType } from '@/types/locale'; export async function generateMetadata({ params: { locale } }: { params: { locale: LocaleCodeType } }) { diff --git a/frontend/src/app/[locale]/account/signup/page.tsx b/frontend/src/app/[locale]/account/signup/page.tsx index eec62e2..9ef869c 100644 --- a/frontend/src/app/[locale]/account/signup/page.tsx +++ b/frontend/src/app/[locale]/account/signup/page.tsx @@ -1,7 +1,7 @@ -import { PageType } from '@/types/base'; -import AuthPage from '../authPage'; import { getTranslations } from 'next-intl/server'; import { useTranslations } from 'next-intl'; +import AuthPage from '../authPage'; +import { PageType } from '@/types/base'; import { LocaleCodeType } from '@/types/locale'; export async function generateMetadata({ params: { locale } }: { params: { locale: LocaleCodeType } }) { diff --git a/frontend/src/app/[locale]/admin/AdminPage.tsx b/frontend/src/app/[locale]/admin/AdminPage.tsx index a5d5ae7..1f04373 100644 --- a/frontend/src/app/[locale]/admin/AdminPage.tsx +++ b/frontend/src/app/[locale]/admin/AdminPage.tsx @@ -1,16 +1,16 @@ 'use client'; -import React from 'react'; import { useState, useEffect, useContext } from 'react'; +import { Button, addToast } from '@heroui/react'; +import UsersTable from './UsersTable'; import { UserType, AdminMessages } from '@/types/user'; import { TokenContext } from '@/utils/TokenProvider'; import { useRouter } from '@/src/i18n/routing'; -import UsersTable from './UsersTable'; import Config from '@/config/config'; import { LocaleCodeType } from '@/types/locale'; import { updateUserRole } from '@/utils/usersControl'; -import { Button, addToast } from '@heroui/react'; import { roles } from '@/config/selection'; import DeleteConfirmDialog from '@/components/DeleteConfirmDialog'; +import { logError } from '@/utils/errorHandler'; const apiServer = Config.apiServer; type Props = { @@ -36,8 +36,8 @@ async function fetchUsers(jwt: string) { } const data = await response.json(); return data; - } catch (error: any) { - console.error('Error fetching data:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } @@ -66,8 +66,8 @@ export default function AdminPage({ messages, locale }: Props) { if (tokenContext.token.user) { setMyself(tokenContext.token.user); } - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching users:', error); } } diff --git a/frontend/src/app/[locale]/admin/UsersTable.tsx b/frontend/src/app/[locale]/admin/UsersTable.tsx index 2cec55d..ea50468 100644 --- a/frontend/src/app/[locale]/admin/UsersTable.tsx +++ b/frontend/src/app/[locale]/admin/UsersTable.tsx @@ -14,9 +14,9 @@ import { DropdownItem, } from '@heroui/react'; import { ChevronDown } from 'lucide-react'; +import Avatar from 'boring-avatars'; import { UserType, AdminMessages } from '@/types/user'; import { roles } from '@/config/selection'; -import Avatar from 'boring-avatars'; type Props = { users: UserType[]; diff --git a/frontend/src/app/[locale]/health/HealthPage.tsx b/frontend/src/app/[locale]/health/HealthPage.tsx index d97cc1e..7a9b83f 100644 --- a/frontend/src/app/[locale]/health/HealthPage.tsx +++ b/frontend/src/app/[locale]/health/HealthPage.tsx @@ -4,6 +4,7 @@ import { Table, TableBody, TableRow, TableHeader, TableCell, Chip, TableColumn } import { LocaleCodeType } from '@/types/locale'; import { HealthMessages } from '@/types/health'; import Config from '@/config/config'; +import { logError } from '@/utils/errorHandler'; const apiServer = Config.apiServer; type Props = { @@ -21,8 +22,8 @@ async function fetchHealth() { } const data = await response.json(); return data; - } catch (error: any) { - console.log('Error fetching health data:', error); + } catch (error: unknown) { + logError('Error fetching health data:', error); } } @@ -38,8 +39,8 @@ export default function HealthPage({ messages, locale }: Props) { const data = await fetchHealth(); setStatus(data.status); setIsFetching(false); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error in effect:', error); } } @@ -61,7 +62,7 @@ export default function HealthPage({ messages, locale }: Props) { {messages.unittcms_version} - 1.0.0-beta.15 + 1.0.0-beta.16 {messages.api_server} diff --git a/frontend/src/app/[locale]/layout.tsx b/frontend/src/app/[locale]/layout.tsx index 2512e37..cb1dcb8 100644 --- a/frontend/src/app/[locale]/layout.tsx +++ b/frontend/src/app/[locale]/layout.tsx @@ -1,12 +1,12 @@ import '@/styles/globals.css'; -import { fontSans } from '@/config/fonts'; -import { Providers } from './providers'; -import Header from './Header'; import clsx from 'clsx'; import { getTranslations } from 'next-intl/server'; import { useTranslations } from 'next-intl'; -import { LocaleCodeType } from '@/types/locale'; import { headers } from 'next/headers'; +import Header from './Header'; +import { Providers } from './providers'; +import { LocaleCodeType } from '@/types/locale'; +import { fontSans } from '@/config/fonts'; export async function generateMetadata({ params: { locale } }: { params: { locale: LocaleCodeType } }) { const headersList = headers(); diff --git a/frontend/src/app/[locale]/page.tsx b/frontend/src/app/[locale]/page.tsx index 0102db1..03ba524 100644 --- a/frontend/src/app/[locale]/page.tsx +++ b/frontend/src/app/[locale]/page.tsx @@ -1,13 +1,12 @@ -import { redirect } from 'next/navigation' +import { redirect } from 'next/navigation'; import { PageType } from '@/types/base'; import Config from '@/config/config'; import LandingPage from '@/src/app/[locale]/LandingPage'; export default function Home({ params }: PageType) { - if (!Config.isDemoSite) { - redirect(`/${params.locale}/projects`) + redirect(`/${params.locale}/projects`); } - return + return ; } diff --git a/frontend/src/app/[locale]/projects/ProjectsPage.tsx b/frontend/src/app/[locale]/projects/ProjectsPage.tsx index c85931f..1b367ad 100644 --- a/frontend/src/app/[locale]/projects/ProjectsPage.tsx +++ b/frontend/src/app/[locale]/projects/ProjectsPage.tsx @@ -2,12 +2,13 @@ import { useEffect, useState, useContext } from 'react'; import { Button } from '@heroui/react'; import { Plus } from 'lucide-react'; +import ProjectsTable from './ProjectsTable'; import { TokenContext } from '@/utils/TokenProvider'; import { ProjectDialogMessages, ProjectType, ProjectsMessages } from '@/types/project'; -import ProjectsTable from './ProjectsTable'; import ProjectDialog from '@/components/ProjectDialog'; import { fetchProjects, createProject } from '@/utils/projectsControl'; import { LocaleCodeType } from '@/types/locale'; +import { logError } from '@/utils/errorHandler'; export type Props = { messages: ProjectsMessages; @@ -27,8 +28,8 @@ export default function ProjectsPage({ messages, projectDialogMessages, locale } try { const data = await fetchProjects(context.token.access_token); setProjects(data); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } diff --git a/frontend/src/app/[locale]/projects/ProjectsTable.tsx b/frontend/src/app/[locale]/projects/ProjectsTable.tsx index 4dbeaa8..75e43d9 100644 --- a/frontend/src/app/[locale]/projects/ProjectsTable.tsx +++ b/frontend/src/app/[locale]/projects/ProjectsTable.tsx @@ -1,8 +1,8 @@ import { useState, useMemo, useCallback, ReactNode } from 'react'; import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, SortDescriptor } from '@heroui/react'; +import dayjs from 'dayjs'; import { Link, NextUiLinkClasses } from '@/src/i18n/routing'; import { ProjectType, ProjectsMessages } from '@/types/project'; -import dayjs from 'dayjs'; import PublicityChip from '@/components/PublicityChip'; import { LocaleCodeType } from '@/types/locale'; @@ -39,35 +39,43 @@ export default function ProjectsTable({ projects, messages, locale }: Props) { return text.length > maxLength ? text.slice(0, maxLength) + '...' : text; }; - const renderCell = useCallback((project: ProjectType, columnKey: string): ReactNode => { - const cellValue = project[columnKey as keyof ProjectType]; + const renderCell = useCallback( + (project: ProjectType, columnKey: string): ReactNode => { + const cellValue = project[columnKey as keyof ProjectType]; - switch (columnKey) { - case 'id': - return {cellValue as number}; - case 'isPublic': - return ( - - ); - case 'name': - const maxLength = 30; - const truncatedDetail = truncateText(project.detail, maxLength); - return ( -
- - {cellValue as string} - -
-
{truncatedDetail}
+ switch (columnKey) { + case 'id': + return {cellValue as number}; + case 'isPublic': + return ( + + ); + case 'name': { + const maxLength = 30; + const truncatedDetail = truncateText(project.detail, maxLength); + return ( +
+ + {cellValue as string} + +
+
{truncatedDetail}
+
-
- ); - case 'updatedAt': - return {dayjs(cellValue as number).format('YYYY/MM/DD HH:mm')}; - default: - return cellValue as string; - } - }, []); + ); + } + case 'updatedAt': + return {dayjs(cellValue as number).format('YYYY/MM/DD HH:mm')}; + default: + return cellValue as string; + } + }, + [locale, messages.private, messages.public] + ); const classNames = useMemo( () => ({ diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/FolderDialog.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/FolderDialog.tsx index 0fd1eb3..ad6d5c3 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/FolderDialog.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/FolderDialog.tsx @@ -1,5 +1,4 @@ 'use client'; -import React from 'react'; import { useState, useEffect } from 'react'; import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@heroui/react'; import { FolderType, FoldersMessages } from '@/types/folder'; @@ -47,6 +46,7 @@ export default function FolderDialog({ isOpen, editingFolder, onCancel, onSubmit text: '', }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [editingFolder]); const clear = () => { diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/FoldersPane.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/FoldersPane.tsx index 6331f04..c0ba0b9 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/FoldersPane.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/FoldersPane.tsx @@ -1,15 +1,16 @@ 'use client'; -import { FolderType, FoldersMessages } from '@/types/folder'; import { useState, useEffect, useContext } from 'react'; import { Button, Listbox, ListboxItem } from '@heroui/react'; import { Folder, Plus } from 'lucide-react'; +import FolderDialog from './FolderDialog'; +import FolderEditMenu from './FolderEditMenu'; +import { fetchFolders, createFolder, updateFolder, deleteFolder } from './foldersControl'; import { usePathname, useRouter } from '@/src/i18n/routing'; import { TokenContext } from '@/utils/TokenProvider'; import useGetCurrentIds from '@/utils/useGetCurrentIds'; -import FolderDialog from './FolderDialog'; -import FolderEditMenu from './FolderEditMenu'; import DeleteConfirmDialog from '@/components/DeleteConfirmDialog'; -import { fetchFolders, createFolder, updateFolder, deleteFolder } from './foldersControl'; +import { FolderType, FoldersMessages } from '@/types/folder'; +import { logError } from '@/utils/errorHandler'; type Props = { projectId: string; @@ -49,13 +50,13 @@ export default function FoldersPane({ projectId, messages, locale }: Props) { const smallestFolderId = Math.min(...folders.map((folder) => folder.id)); router.push(`/projects/${projectId}/folders/${smallestFolderId}/cases`, { locale: locale }); } - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching folders:', error); } } fetchDataEffect(); - }, [context, folderId]); + }, [context, folderId, locale, pathname, projectId, router]); const openDialogForCreate = () => { setIsFolderDialogOpen(true); diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CaseDialog.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CaseDialog.tsx index f68fa7f..e718d96 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CaseDialog.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CaseDialog.tsx @@ -1,6 +1,5 @@ 'use client'; -import React from 'react'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@heroui/react'; import { CasesMessages } from '@/types/case'; diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx index 26558cf..dd06ccd 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx @@ -8,6 +8,7 @@ import { CaseType, CasesMessages } from '@/types/case'; import DeleteConfirmDialog from '@/components/DeleteConfirmDialog'; import { PriorityMessages } from '@/types/priority'; import { LocaleCodeType } from '@/types/locale'; +import { logError } from '@/utils/errorHandler'; type Props = { projectId: string; @@ -30,8 +31,8 @@ export default function CasesPane({ projectId, folderId, messages, priorityMessa try { const data = await fetchCases(context.token.access_token, Number(folderId)); setCases(data); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching cases:', error); } } diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx index cd5242e..791b4f3 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx @@ -15,7 +15,7 @@ import { SortDescriptor, ButtonGroup, } from '@heroui/react'; -import { Plus, MoreVertical, Trash, Download, FileDown, ChevronDown, FileJson, FileSpreadsheet } from 'lucide-react'; +import { Plus, MoreVertical, Trash, FileDown, ChevronDown, FileJson, FileSpreadsheet } from 'lucide-react'; import { Link } from '@/src/i18n/routing'; import { CaseType, CasesMessages } from '@/types/case'; import { PriorityMessages } from '@/types/priority'; @@ -122,6 +122,7 @@ export default function TestCaseTable({ default: return cellValue as string; } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleDeleteCases = () => { @@ -180,7 +181,6 @@ export default function TestCaseTable({ - ); - default: - return cellValue; - } - }, []); + switch (columnKey) { + case 'avatar': + return ( + + ); + case 'email': + return cellValue; + case 'username': + return cellValue; + case 'add': + return ( + + ); + default: + return cellValue; + } + }, + [messages.add, onAddPress] + ); const classNames = useMemo( () => ({ diff --git a/frontend/src/app/[locale]/projects/[projectId]/members/MembersPage.tsx b/frontend/src/app/[locale]/projects/[projectId]/members/MembersPage.tsx index d46a864..4383e68 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/members/MembersPage.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/members/MembersPage.tsx @@ -1,23 +1,22 @@ 'use client'; -import React from 'react'; import { useState, useEffect, useContext } from 'react'; import { Button } from '@heroui/react'; import { Plus } from 'lucide-react'; -import { MemberType, UserType } from '@/types/user'; -import { MembersMessages } from '@/types/member'; -import { TokenContext } from '@/utils/TokenProvider'; import MembersTable from './MembersTable'; import AddMemberDialog from './AddMemberDialog'; import { fetchProjectMembers, addMember, deleteMember, updateMember } from './membersControl'; +import { MemberType, UserType } from '@/types/user'; +import { MembersMessages } from '@/types/member'; +import { TokenContext } from '@/utils/TokenProvider'; import DeleteConfirmDialog from '@/components/DeleteConfirmDialog'; +import { logError } from '@/utils/errorHandler'; type Props = { projectId: string; messages: MembersMessages; - locale: string; }; -export default function MembersPage({ projectId, messages, locale }: Props) { +export default function MembersPage({ projectId, messages }: Props) { const context = useContext(TokenContext); const [members, setMembers] = useState([]); const [isDialogOpen, setIsDialogOpen] = useState(false); @@ -31,13 +30,13 @@ export default function MembersPage({ projectId, messages, locale }: Props) { try { const data = await fetchProjectMembers(context.token.access_token, projectId); setMembers(data); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching members:', error); } } fetchDataEffect(); - }, [context]); + }, [context, projectId]); const handleAddMember = async (userAdded: UserType) => { if (userAdded.id) { diff --git a/frontend/src/app/[locale]/projects/[projectId]/members/MembersTable.tsx b/frontend/src/app/[locale]/projects/[projectId]/members/MembersTable.tsx index f036ce9..ad046d7 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/members/MembersTable.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/members/MembersTable.tsx @@ -14,9 +14,9 @@ import { DropdownItem, } from '@heroui/react'; import { ChevronDown } from 'lucide-react'; +import Avatar from 'boring-avatars'; import { MemberType, UserType } from '@/types/user'; import { memberRoles } from '@/config/selection'; -import Avatar from 'boring-avatars'; import { MembersMessages } from '@/types/member'; type Props = { diff --git a/frontend/src/app/[locale]/projects/[projectId]/members/membersControl.ts b/frontend/src/app/[locale]/projects/[projectId]/members/membersControl.ts index c1f975f..216b1b4 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/members/membersControl.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/members/membersControl.ts @@ -1,3 +1,4 @@ +import { logError } from '@/utils/errorHandler'; import Config from '@/config/config'; const apiServer = Config.apiServer; @@ -19,8 +20,8 @@ async function fetchProjectMembers(jwt: string, projectId: string) { } const data = await response.json(); return data; - } catch (error: any) { - console.error('Error fetching data:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } @@ -42,8 +43,8 @@ async function addMember(jwt: string, userId: number, projectId: number) { } const data = await response.json(); return data; - } catch (error: any) { - console.error('Error fetching data:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } @@ -63,8 +64,8 @@ async function deleteMember(jwt: string, userId: number, projectId: number) { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } - } catch (error: any) { - console.error('Error fetching data:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } @@ -86,8 +87,8 @@ async function updateMember(jwt: string, userId: number, projectId: number, role } const data = await response.json(); return data; - } catch (error: any) { - console.error('Error fetching data:', error.message); + } catch (error: unknown) { + logError('Error fetching data:', error); } } diff --git a/frontend/src/app/[locale]/projects/[projectId]/members/page.tsx b/frontend/src/app/[locale]/projects/[projectId]/members/page.tsx index 465daaa..9816f30 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/members/page.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/members/page.tsx @@ -1,7 +1,7 @@ -import { LocaleCodeType } from '@/types/locale'; -import MembersPage from './MembersPage'; import { getTranslations } from 'next-intl/server'; import { useTranslations } from 'next-intl'; +import MembersPage from './MembersPage'; +import { LocaleCodeType } from '@/types/locale'; export async function generateMetadata({ params: { locale } }: { params: { locale: LocaleCodeType } }) { const t = await getTranslations({ locale, namespace: 'Members' }); @@ -34,7 +34,7 @@ export default function Page({ params }: { params: { projectId: string; locale: return ( <> - + ); } diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/RunDialog.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/RunDialog.tsx index 46c096f..7744a5e 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/runs/RunDialog.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/RunDialog.tsx @@ -1,5 +1,4 @@ 'use client'; -import React from 'react'; import { useState, useEffect } from 'react'; import { Button, Input, Textarea, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@heroui/react'; import { RunType, RunsMessages } from '@/types/run'; @@ -47,6 +46,7 @@ export default function RunDialog({ isOpen, editingRun, onCancel, onSubmit, mess text: '', }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [editingRun]); const clear = () => { diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/RunsPage.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/RunsPage.tsx index dac54b2..f2810fa 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/runs/RunsPage.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/RunsPage.tsx @@ -9,6 +9,7 @@ import { RunType, RunsMessages } from '@/types/run'; import DeleteConfirmDialog from '@/components/DeleteConfirmDialog'; import { TokenContext } from '@/utils/TokenProvider'; import { LocaleCodeType } from '@/types/locale'; +import { logError } from '@/utils/errorHandler'; type Props = { projectId: string; @@ -61,13 +62,13 @@ export default function RunsPage({ projectId, locale, messages }: Props) { try { const data = await fetchRuns(context.token.access_token, Number(projectId)); setRuns(data); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching runs', error); } } fetchDataEffect(); - }, [context]); + }, [context, projectId]); const onSubmit = async (name: string, description: string) => { if (editingRun && editingRun.createdAt) { diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/RunsTable.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/RunsTable.tsx index 955bfaf..f135b84 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/runs/RunsTable.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/RunsTable.tsx @@ -13,10 +13,10 @@ import { DropdownItem, SortDescriptor, } from '@heroui/react'; -import { Link, NextUiLinkClasses } from '@/src/i18n/routing'; import { MoreVertical } from 'lucide-react'; -import { RunsMessages, RunType } from '@/types/run'; import dayjs from 'dayjs'; +import { Link, NextUiLinkClasses } from '@/src/i18n/routing'; +import { RunsMessages, RunType } from '@/types/run'; import { LocaleCodeType } from '@/types/locale'; type Props = { @@ -70,7 +70,7 @@ export default function RunsTable({ projectId, isDisabled, runs, onDeleteRun, me switch (columnKey) { case 'id': return {cellValue as number}; - case 'name': + case 'name': { const maxLength = 30; const truncatedDescription = truncateText(run.description, maxLength); return ( @@ -83,6 +83,7 @@ export default function RunsTable({ projectId, isDisabled, runs, onDeleteRun, me
); + } case 'updatedAt': return {dayjs(cellValue as string).format('YYYY/MM/DD HH:mm')}; case 'actions': diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx index b362969..62ad1da 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx @@ -55,6 +55,7 @@ import { useFormGuard } from '@/utils/formGuard'; import { PriorityMessages } from '@/types/priority'; import { RunStatusMessages, TestRunCaseStatusMessages } from '@/types/status'; import { TestTypeMessages } from '@/types/testType'; +import { logError } from '@/utils/errorHandler'; const defaultTestRun = { id: 0, @@ -132,12 +133,13 @@ export default function RunEditor({ setFolders(foldersData); setSelectedFolder(foldersData[0]); initTestCases(); - } catch (error: any) { - console.error('Error in effect:', error.message); + } catch (error: unknown) { + logError('Error fetching run data', error); } } fetchDataEffect(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [tokenContext]); useEffect(() => { @@ -146,8 +148,8 @@ export default function RunEditor({ try { const filteredData = testCases.filter((testCase) => testCase.folderId === selectedFolder.id); setFilteredTestCases(filteredData); - } catch (error: any) { - console.error('Error fetching cases data:', error.message); + } catch (error: unknown) { + logError('Error filtering test cases', error); } } } @@ -224,7 +226,6 @@ export default function RunEditor({