A Node.js API for managing tasks in Asana, built with Express.js, TypeScript, and PNPM.
-
First Submission Demonstration
Description: Demonstration of the initial submission showcasing core functionalities. -
Final Task Update Demonstration
Description: Demonstration of real-time functionality using webhooks with additional implementations.
-
POST
/api/tasks
- Create a new task with a due date and priority.
- Triggered manually for task creation and verification.
-
GET
/api/tasks/in-progress
- Retrieve all tasks currently in the "In Progress" section.
- Triggered manually for verification of tasks in the "In Progress" section.
-
GET
/api/tasks/:projectId
- Fetch all tasks associated with a specific project.
- Triggered manually for verification of tasks in a specific project.
-
PATCH
/api/tasks/:id/progress
- Move a task to the "In Progress" section and update due dates and extensions if the task is high priority.
- Triggered when a user moves a task to the "In Progress" section in the Asana UI.
-
PATCH
/api/tasks/:id/fix
- Automatically add the extension as
True
when a task is created or moved in the default section without a priority. - Triggered when a task is created or moved in the default section in the Asana UI.
- Automatically add the extension as
-
PATCH
/api/tasks/:id/priority
- Automatically update the due date for a task in the default section based on the priority selected.
- Triggered when a user updates the priority of a task in the Asana UI.
-
PUT
/api/tasks/:id/remove
- When a task is moved out of the "In Progress" section, reduce the due dates for all tasks in the section by 2 days.
- Triggered when a task is moved out of the "In Progress" section in the Asana UI.
Ensure you have the following installed on your system:
-
Node.js: Version 14.x or higher
-
Express.js: Version 5.xx
-
TypeScript: ES2020 support
-
PNPM: Latest version
-
Environment Variables:
- Create a
.env
file in the root directory with the following: - Refer the
.env.example
which was done through dotenv-safe.
ASANA_ACCESS_TOKEN= IN_PROGRESS_SECTION_ID= PORT= DEFAULT_SECTION_ID= DEFAULT_PROJECT_ID= WORKSPACE_ID= PRIORITY_CUSTOM_FIELD_ID= PRIORITY_LOW_ID= PRIORITY_MEDIUM_ID= PRIORITY_HIGH_ID= EXTENSION_PROCESSED_FIELD_ID= TRUE_ENUM_GID= FALSE_ENUM_GID= BASE_API_URL=
- Create a
Below are the required CURL commands to retrieve necessary details for configuring your .env
file.
-
Access Token: Generate the Asana Access Token directly from the Asana Developer Console. This value is not retrieved via API.
-
WORKSPACE_ID:
curl -X GET "https://app.asana.com/api/1.0/workspaces" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>"
-
DEFAULT_PROJECT_ID
curl -X GET "https://app.asana.com/api/1.0/projects?workspace=<WORKSPACE_ID>" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>"
-
IN_PROGRESS_SECTION_ID
curl -X GET "https://app.asana.com/api/1.0/projects/<PROJECT_ID>/sections" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>"
-
The "Priority" custom field will be an enum type with options: Low, Medium, and High.
curl -X POST "https://app.asana.com/api/1.0/custom_fields" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "data": { "name": "Priority", "type": "enum", "enum_options": [ { "name": "Low", "enabled": true }, { "name": "Medium", "enabled": true }, { "name": "High", "enabled": true } ], "workspace": "<WORKSPACE_ID>" } }'
-
The "Extension Processed" field will be an enum type with options true and false.
curl -X POST "https://app.asana.com/api/1.0/custom_fields" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "data": { "name": "Extension Processed", "type": "enum", "enum_options": [ { "name": "true", "enabled": true }, { "name": "false", "enabled": true } ], "workspace": "<WORKSPACE_ID>" } }'
-
Attach the "Priority" Custom Field
curl -X POST "https://app.asana.com/api/1.0/projects/<PROJECT_ID>/addCustomFieldSetting" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "data": { "custom_field": "<PRIORITY_CUSTOM_FIELD_ID>", // Replace with the ID of the "Priority" field "is_important": true } }'
-
Attach the "Extension Processed" Custom Field
curl -X POST "https://app.asana.com/api/1.0/projects/<PROJECT_ID>/addCustomFieldSetting" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>" \ -H "Content-Type: application/json" \ -d '{ "data": { "custom_field": "<EXTENSION_PROCESSED_FIELD_ID>", // Replace with the ID of the "Extension Processed" field "is_important": false } }'
-
PRIORITY_CUSTOM_FIELD_ID, PRIORITY_LOW_ID, PRIORITY_MEDIUM_ID and PRIORITY_HIGH_ID
curl -X GET "https://app.asana.com/api/1.0/projects/<project_id>/custom_field_settings" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>"
-
EXTENSION_PROCESSED_FIELD_ID, TRUE_ENUM_GID and FALSE_ENUM_GID
curl -X GET "https://app.asana.com/api/1.0/projects/<project_id>/custom_field_settings" \ -H "Authorization: Bearer <ASANA_ACCESS_TOKEN>"
-
Clone the repository:
git clone https://github.com/VoidGeek/PRADYUMNA-P-DYNAMIC-DEADLINE-SAHYADRI.git cd PRADYUMNA-P-DYNAMIC-DEADLINE-SAHYADRI
-
Install Dependencies Install all required packages using PNPM:
pnpm install
-
Compile the Code (for Production) Build the TypeScript code into JavaScript:
pnpm build
-
Run the Development Server Start the development server with hot-reloading:
pnpm dev
-
Run the Development Server in Strict Mode(Optional) Start the development server with additional strict type checks:
pnpm dev:strict
-
Run in Production Mode (Optional) Start the compiled application in production mode:
pnpm start
-
Clean the Project (Optional) Remove compiled files and reset the project:
pnpm clean
pnpm dev
- Runs the application in development mode usingts-node-dev
with hot-reloading.pnpm dev:strict
- Runs a full TypeScript type-check (tsc --noEmit
) and then starts the development server.pnpm build
- Compiles the TypeScript source code into JavaScript.pnpm start
- Runs the compiled application in production mode fromdist/index.js
.pnpm clean
- Deletes thedist
directory and removes compiled.js
files.
-
Global Utilities:
AppError
: Centralized error-handling class for consistent API error responses.sendResponse
: Standardized response utility for returning consistent JSON responses.logMessage
: A structured, color-coded logging utility for enhanced debugging.wrappedRouter
: A utility that extends the Express Router to automatically register routes in a centralized registry for better API tracking and debugging in logs.
-
Task Progress Management:
- Move tasks to the "In Progress" section for better tracking.
-
Task Retrieval:
- Fetch all tasks from a specific project.
- Retrieve tasks that are currently in the "In Progress" section.
-
Error Handling:
- Centralized error handling for API failures and invalid requests.
- User-friendly error messages with proper HTTP status codes.
-
Custom Field Management:
- Created two custom fields:
Priority
andExtension Processed
. - Attached these custom fields to relevant projects using Asana’s API.
- Created two custom fields:
-
Task Creation Workflow:
- Tasks without priority skip due date calculation and have "Extension Processed" set to
true
. - Tasks with a valid priority have due dates calculated based on the priority and "Extension Processed" set to
false
. - Priority is validated against allowed values (
Low
,Medium
,High
). Invalid inputs result in a400 Bad Request
error.
- Tasks without priority skip due date calculation and have "Extension Processed" set to
-
Task Movement Workflow:
- Fetched all tasks in the "In Progress" section.
- Filtered tasks to exclude those with
High
priority, already processed extensions, or undefined priority. - Updated eligible tasks by assigning new due dates and marking them as processed.
-
Logging Enhancements:
- Implemented structured logging with levels:
DEBUG
,INFO
,WARN
, andERROR
. - Logged task creation, updates, and error scenarios for better debugging and transparency.
- Implemented structured logging with levels:
-
Error Handling Improvements:
- Centralized error handling for consistent feedback.
- Missing fields or invalid custom field values trigger descriptive error messages with proper status codes.
- Assumption: Custom fields could be globally attached to a workspace, making them automatically available for all projects.
- Outcome: This approach failed, as Asana requires explicit attachment of custom fields to each project.
- Attempt: -Dynamically assign custom fields during task creation.
- Outcome: -This approach proved overly complex and less efficient than pre-attaching fields to projects.
- Assumption:
Asana's default handling for dropdown fields would automatically apply a
"None"
value. - Outcome: Runtime issues arose, leading to explicit handling of default priority values and consistent custom field settings.
- Initial Implementation: Redundantly fetched and updated all tasks in a section, resulting in performance bottlenecks.
- Outcome: Optimized filtering logic to skip unnecessary updates by pre-checking the priority and processed status of tasks.
- Initial Approach: Relied solely on TypeScript interfaces for validating priority values.
- Outcome: TypeScript validation was insufficient for invalid inputs received via API requests. Added explicit validation in the controllers for runtime checks.
I have added some improvements upon feedback.
Make sure the server
is running at all times and only then complete the following steps.
-
Webhook Integration with ngrok
Integrated webhooks usingngrok
to expose a local server, enabling seamless communication between Asana and the application.ngrok http <port>
-
Webhook Registration
Used acurl
command to register the webhook URL with Asana. The entire process is automated, ensuring smooth and efficient task updates.curl --request POST \ --url https://app.asana.com/api/1.0/webhooks \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --header 'Authorization: Bearer <ASANA_ACCESS_TOKEN>' \ --data ' { "data": { "resource": "<DEFAULT_PROJECT_ID>", "target": "<WebhookURL>/webhook" } } '
-
Dynamic Due Date Adjustment
- When a task is moved out of the "In Progress" section, the due dates for all remaining tasks in the section are automatically reduced by 2 days.
- This functionality is triggered using the webhook event listener:
if ( event.action === "removed" && parentId === process.env.IN_PROGRESS_SECTION_ID && event.resource?.resource_type === "task" ) { await axios.put(${BASE_API_URL}/api/tasks/${taskId}/remove); }
-
Websocket vs Webhook
- Webhooks for real-time, event-driven updates, making them ideal for triggering specific actions when tasks or projects are modified.
- WebSockets are not supported by Asana, as they are designed for continuous, bidirectional communication, which is unnecessary for Asana's event-based workflow.
-
Fully Automated Workflow
- After registration, updates are processed automatically without manual intervention.
- Ensures seamless interaction with the Asana UI, reducing maintenance.