Challenge Overview
Implement the ability to breakdown price using project settings.
Project Background
Topcoder Project Service is the main backend service of Topcode Connect – client-facing application of Topcoder.
When customers create projects using the project creation wizard they choose various features which we call “Building Blocks”. When a new project is saved we store the price of each building block in the DB in ProjectEstimation model. So as each project may have several building blocks chosen, there could be several ProjectEstimation records per project.
Each ProjectEstimation record so far keeps only the total price of the corresponding building block. We would like to have the ability to breakdown the total price of each ProjectEstimation record into different kind of fees like “community budget”, “Topcoder service fee”, “reference program fee” and so on.
Technology Stack
-
Node.js
-
PostgreSQL
Code access
The work for this challenge has to be done in one repository:
- Project Service repo https://github.com/topcoder-platform/tc-project-service branch feature/price-estimation-items/project-settings.
- Config for local setup is provided on the forum.
Individual requirements
Hint: whenever below we require to check readPermission or writePermission, we already have a reusable method to check permissions hasPermissionForProject which can be used.
1. Project Estimation Items
-
[Major requirement] Create ProjectEstimationItems model to store ProjectEstimation breakdown:
- id bigint NOT NULL,
- "projectEstimationId" bigint NOT NULL, // reference to ProjectEstimation model
- price double precision NOT NULL,
- type enum(community, topcoder_services, fee) NOT NULL, // all values of ESTIMATION_TYPE
- "markupUsedReference" string NOT NULL, // will be equal to the string “projectSetting” for project settings
- "markupUsedReferenceId" bigint NOT NULL, // will hold the “id” of a projectSettings record for project settings
- metadata json NOT NULL DEFAULT '{}'::json,
- "deletedAt" timestamp with time zone,
- "createdAt" timestamp with time zone NOT NULL,
- "updatedAt" timestamp with time zone NOT NULL,
- "deletedBy" bigint,
- "createdBy" bigint NOT NULL,
- "updatedBy" bigint NOT NULL,
2. Project Settings
-
[Major requirement] Create ProjectSettings model:
id bigint NOT NULL,
key varchar(255), // e.g. 'markup_community'
value varchar(255),
valueType enum(int, double, string, percentage),
"projectId" bigint NOT NULL,
metadata json NOT NULL DEFAULT '{}'::json,
"readPermission": json NOT NULL DEFAULT '{}'::json,
"writePermission": json NOT NULL DEFAULT '{}'::json,
"deletedAt" timestamp with time zone,
"createdAt" timestamp with time zone,
"updatedAt" timestamp with time zone,
"deletedBy" bigint,
"createdBy" bigint NOT NULL,
"updatedBy" bigint NOT NULL,
CONSTRAINT UNIQUE (key, projectId) -
[Major requirement] Implement CRUD endpoints:
- GET /projects/{id}/settings - permission to access this endpoint projectView, additionaly filter returned records using “readPermission” - only users who match “readPermission” will get such records.
- POST /projects/{id}/settings - permission to access this endpoint connectManagersOrAdmins
- PATCH /projects/{id}/settings/{settingsId} - permission to access this endpoint: only users who match “writePermission” can update settings
- DELETE /projects/{id}/settings/{settingsId} - permission to access this endpoint connectManagersOrAdmins
See examples of read/write permissions. -
[Major requirement] As ProjectSettings may contain sensitive information, we should make sure that data cannot be accidentally accessed by users with no proper permissions. In particular, we should take precautions so future developers don’t include such information accidentally by making some changes.
- to make sure that we only users with proper permissions get data from the ProjectSettings model we can use beforeFind or beforeFindAfterOptions hook.
- This hook should get reqUser = req.authUser object through options when we call findOne/findAll. If reqUser object is not provided by options, we should throw an error.
- If the user object is provided we should use it to update find filter records as per user permissions and “readPermission” of the records
- The method described above is my proposition. Any improvements which could make sure future developers don’t return non-filtered data in future are highly welcome. -
Sometimes we may need to get all ProjectSettings records to make calculations inside the Project Service code. To retrieve it for internal usage, support option “includeAllProjectSettingsForInternalUsage”, which we can pass via options for findAll or findOne method instead of reqUser.
-
Implement unit tests for these endpoints a similar way we do for others.
- [Major requirement] In particular, write extensive unit tests with positive and negative cases to verify that only allowed records are returned to the users. And only allowed users may perform operations.
3. Calculate Project Estimation Items
-
ProjectSettings may contain various settings. In this task we would be interested in ProjectSettings which would configure price breakdown. Such settings have “key” = “markup_<any of ESTIMATION_TYPE>”. Example: “key”=”markup_fee”.
-
[Major requirement] Every time when ProjectSettings for price breakdown are changed (added, edited or deleted) we should soft-delete all ProjectEstimationItems records for the project for which ProjectSettings has been changed and create new project estimation items with the next algorithm:
- find all ProjectSettings records with key “markup_<any of ESTIMATION_TYPE>”.
- now for each ProjectEstimation record of the project, we should create ProjectEstimationItems records per each price type with “ProjectEstimationItem.type = <ESTIMATION_TYPE>”
- if “ProjectSetting.valueType” is “double”, then set “ProjectEstimationItem.price” = “ProjectSetting.value”
- if “ProjectSetting.valueType” is “percentage”, then set “ProjectEstimationItem.price” = “percantage from the ProjectEstimation.price” -
For example, if we have ProjectEstimation and next ProjectSettings records, then we would create 3 ProjectEstimationItem records.
-
Put this logic into a separate function.
-
Implement unit tests to verify, that recalculation is happening when ProjectSettings for price breakdown are changed (created/edited/deleted). And that recalculation is done correctly as per algorithm above in various situations.
General requirements
-
Create corresponding SQL migration script in /migrations folder to support model changes in the DB.
Validate that migration script is correct and submission works if we use migration SQL over the old DB structure. -
Follow the existent code standards and approaches.
-
Update Swagger. Add new endpoints and create definitions where necessary.
-
Update Postman file. Add new endpoints with valid data so they can be called for easy testing.
-
Existent unit tests should pass.
-
Lint should pass.
Verification
Hint
We have a script to populate some demo data https://github.com/topcoder-platform/tc-project-service#import-sample-metadata--projects which could be helpful, as it populates projects and projectTemplates.
Requirement
Provide a verification guide on how to test the submission. In particular, include steps we should follow to test permissions “Project Settings” endpoints and price recalculation logic.
Provide demo data to populate ProjectEstimation model for testings.
Final Submission Guidelines
-
Git patch.
-
Verification guide.