Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 8 additions & 7 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts', '!src/index.ts'],
coverageDirectory: 'coverage',
};
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/tests"],
testMatch: ["**/*.test.ts"],
collectCoverageFrom: ["src/**/*.ts", "!src/index.ts"],
coverageDirectory: "coverage",
setupFiles: ["<rootDir>/tests/setup.ts"],
};
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"type-check": "tsc --noEmit",
"validate": "npm run type-check && npm run lint && npm run format:check",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"unit-tests": "jest --testPathPattern=unit --passWithNoTests",
"integration-tests": "jest --testPathPattern=integration --passWithNoTests",
"test:watch": "jest --watch --testPathPattern=unit",
"test:coverage": "jest --coverage --passWithNoTests --runInBand",
"prisma:generate": "prisma generate --schema prisma",
"prisma:push": "prisma db push --schema prisma",
Expand Down
2 changes: 1 addition & 1 deletion prisma/models/project.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ model Project {
scanBatches ScanBatch[] @relation("ProjectScanBatches")
treeScans TreeScan[] @relation("ProjectTreeScans")

userProjectRoles UserProjectRole[]
userProjectRoles UserProjectRole[]

@@index([countryId])
@@index([adminLocationId])
Expand Down
2 changes: 1 addition & 1 deletion prisma/models/user.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ model User {
correctedTreeScans TreeScan[] @relation("CorrectedTreeScans")
treeScanAudits TreeScanAudit[] @relation("TreeScanAuditChangedBy")

userProjectRoles UserProjectRole[] @relation("UserProjectRoles")
userProjectRoles UserProjectRole[] @relation("UserProjectRoles")
assignedProjectRoles UserProjectRole[] @relation("AssignedByUser")

@@index([roleId])
Expand Down
1 change: 1 addition & 0 deletions src/modules/user-project-roles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as userProjectRolesRoutes } from "./userProjectRole.routes";
68 changes: 68 additions & 0 deletions src/modules/user-project-roles/userProjectRole.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { NextFunction, Request, Response } from "express";
import {
userProjectRoleService,
type CreateUserProjectRoleInput,
} from "./userProjectRole.service";

type AuthenticatedUser = {
id: number;
role: string;
};

export class UserProjectRoleController {
async getRoles(req: Request, res: Response, next: NextFunction) {
try {
const user = req.user as unknown as AuthenticatedUser;

const roles = await userProjectRoleService.getAssignments(
user.id,
user.role,
);

return res.status(200).json({
success: true,
data: roles,
});
} catch (error) {
return next(error);
}
}

async createUserProjectRole(req: Request, res: Response, next: NextFunction) {
try {
const payload = req.body as CreateUserProjectRoleInput;

const role = await userProjectRoleService.createUserProjectRole(payload);

return res.status(201).json({
success: true,
data: role,
});
} catch (error) {
return next(error);
}
}

async deleteUserProjectRole(req: Request, res: Response, next: NextFunction) {
try {
const userId = Number(req.params.user_id);
const projectId = Number(req.params.project_id);
const roleId = Number(req.params.role_id);

const result = await userProjectRoleService.deleteUserProjectRole(
userId,
projectId,
roleId,
);

return res.status(200).json({
success: true,
data: result,
});
} catch (error) {
return next(error);
}
}
}

export const userProjectRoleController = new UserProjectRoleController();
144 changes: 144 additions & 0 deletions src/modules/user-project-roles/userProjectRole.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Router } from "express";
import { authMiddleware } from "../../middleware/auth.middleware";
import { roleMiddleware } from "../../middleware/role.middleware";
import { userProjectRoleController } from "./userProjectRole.controller";

const router = Router();

/**
* @swagger
* tags:
* - name: User Project Roles
* description: Endpoints for managing user roles within projects
*/

/**
* @swagger
* /user-project-roles:
* get:
* summary: Retrieve user project role assignments
* tags: [User Project Roles]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: User project roles retrieved successfully
* 401:
* description: Authentication required
* 403:
* description: Insufficient permissions
*/
router.get(
"/",
authMiddleware,
roleMiddleware(["ADMIN", "MANAGER"]),
(req, res, next) => {
void userProjectRoleController.getRoles(req, res, next);
},
);

/**
* @swagger
* /user-project-roles:
* post:
* summary: Assign a role to a user within a project
* tags: [User Project Roles]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - userId
* - projectId
* - roleId
* - assignedBy
* properties:
* userId:
* type: integer
* projectId:
* type: integer
* roleId:
* type: integer
* assignedBy:
* type: integer
* example:
* userId: 1
* projectId: 10
* roleId: 2
* assignedBy: 4
* responses:
* 201:
* description: User project role assigned successfully
* 400:
* description: Invalid request body
* 401:
* description: Authentication required
* 403:
* description: Insufficient permissions
* 404:
* description: User, project, or role not found
* 409:
* description: Role assignment already exists
*/
router.post(
"/",
authMiddleware,
roleMiddleware(["ADMIN"]),
(req, res, next) => {
void userProjectRoleController.createUserProjectRole(req, res, next);
},
);

/**
* @swagger
* /user-project-roles/{user_id}/{project_id}/{role_id}:
* delete:
* summary: Remove a role assignment from a user within a project
* tags: [User Project Roles]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: user_id
* required: true
* schema:
* type: integer
* description: User ID
* - in: path
* name: project_id
* required: true
* schema:
* type: integer
* description: Project ID
* - in: path
* name: role_id
* required: true
* schema:
* type: integer
* description: Role ID
* responses:
* 200:
* description: User project role removed successfully
* 400:
* description: Invalid user, project, or role ID
* 401:
* description: Authentication required
* 403:
* description: Insufficient permissions
* 404:
* description: Role assignment not found
*/
router.delete(
"/:user_id/:project_id/:role_id",
authMiddleware,
roleMiddleware(["ADMIN"]),
(req, res, next) => {
void userProjectRoleController.deleteUserProjectRole(req, res, next);
},
);

export default router;
Loading
Loading