A web app to create, assign and manage tasks, built with the latest web technologies.
See a live deployment running on my Raspberry Pi here.
- Managers can create and edit tasks for themselves and their subordinates
- Users are notified when new tasks are assigned to them, when they are due soon or overdue
- Using comments, users can ask questions, provide updates and mention other users
- Upon task completion, users mark tasks as Completed and add their remarks
- Managers are notified of Completed tasks, review them and Close the tasks
- Administrators can make any change to any tasks
- All changes are logged and can be viewed by anyone for full transparency
Defaults to the user's system preference and preference is saved in local storage.
Users see their pending tasks and tasks to review if they are managers, as well as company-wide statistics.
Mention users in comments to notify them by email.
Assigned tasks, due soon tasks, overdue tasks and more email notifications.
Create and modify users and send password reset emails.
![]()
Users receive welcome and password reset emails and set their own passwords.
Administrators cannot set or ever see users' passwords which are hashed using Argon2id, an industry-recognized hashing algorithm, thus enforcing nonrepudiation.
All features available on mobile devices.
See all changes made to tasks, enforcing accountability.
- Rate limiting to prevent brute force attacks on both IP and email, with separate limits
- Tasks source and completion attachments with automatic image previews and download links
- Metaframework: Next.js
- Database: SQLite
- ORM: Prisma
- UI components: shadcn-ui
- CSS framework: Tailwind CSS
- Authentication: Lucia and Oslo. Thanks to Robin Wieruch for his excellent tutorial
- Email: Resend
- Install Node.js
- Clone the repo
git clone npm installto install all dependencies- Create a
.env.localfile with the following content:
RESEND_API_KEY="re_123"
BASE_URL="https://example.com"
EMAILS_FROM="Task Tracker <tasks@tasks.example.com>"
DAILY_TASKS_TOKEN="f7238d8c2b7da7a72f93de486dtc707f09a184b0f70"
DATABASE_URL="file:../db/database.db" # Change to file:/var/task-tracker/db/database.db in production
FILES_PATH="./files" # Change to /var/task-tracker/files in production
LOGS_PATH="./logs" # Change to /var/logs/task-tracker in production
MAX_FAILED_ATTEMPTS_EMAIL=10
MAX_FAILED_ATTEMPTS_IP=50
LOCKOUT_MINUTES=15
DEPLOYMENT="blue"
PORT=3001
DUE_SOON_DAYS="10"
OVERDUE_FOR_MORE_THAN_DAYS="5"
OVERDUE_REMINDER_EVERY_DAYS="7"Example in /config_examples
Enter your Resend API key and your base URL.
Use any DAILY_TASKS_TOKEN you want. This is a secret key to call the daily tasks API so it cannot be executed remotely.
- Create a
.env.testfile. Examples in/config_examples - Open
setup.shand changeAPP_USERto the Linux environment user - Update
DATABASE_URL,FILES_PATHandLOGS_PATHin.env.localand.env.testas required - If deployed on Linux, run
./setup.shto create the database and files folders and set their permissions. The folders in the paths defined in.envwill be created. If not deploying on Linux, create the paths folders manually - Run
npx prisma migrate deployto inialize the database npm run devto run in dev modenpm run buildto build the app andnpm startto run in production modenpx prisma studioand manually add the statuses:
| ID | Name | Display Name | Color |
|---|---|---|---|
| 1 | In Progress | In Progress | Blue |
| 2 | Completed | Pending Review | Green |
| 3 | Closed | Closed | Gray |
| 4 | Cancelled | Cancelled | Yellow |
| 5 | Overdue | Overdue | Red |
- Create a service file
/etc/systemd/system/task-tracker.servicewith the following content:
[Unit]
Description=Task Tracker
After=network.target
[Service]
Type=simple
User=appuser
Group=appuser
Restart=on-failure
RestartSec=10
WorkingDirectory=/path/to/app/task-tracker/
StandardOutput=/var/log/task-tracker.log
StandardError=/var/log/task-tracker.log
ExecStart=/usr/bin/npm start
[Install]
WantedBy=multi-user.target- Create the log file with
sudo touch /var/log/task-tracker.log - Ensure the logfile has the user permissions with
sudo chmod 644 /var/log/task-tracker.log - Restart the systemctl background process with
sudo systemctl daemon-reload - Start the service with
sudo systemctl start task-tracker - Enable the service to start at boot with
sudo systemctl enable task-tracker - Check the status with
sudo systemctl status task-tracker - If running a blue-green deployment, append
-blueor-greento the service name
An email worker runs continuously in the background to send emails.
- Create a service file
/etc/systemd/system/email-worker.service:
[Unit]
Description=Email worker
After=network.target
[Service]
User=appuser
Group=appuser
Restart=on-failure
RestartSec=10
WorkingDirectory=/path/to/app/task-tracker
ExecStart=/usr/bin/npm run email-worker
[Install]
WantedBy=multi-user.target- Reload systemd to pick up the new file:
sudo systemctl daemon-reload- Enable the service to start at boot:
sudo systemctl enable email-worker.service- Start the service:
sudo systemctl start email-worker.service- Verify it's running:
sudo systemctl status email-worker.serviceSchedule to run npm run daily to run all the daily tasks, just after midnight. This will clear unused password reset tokens and check for overdue and due soon tasks and send the email notifications.
You can use a cron job to run the script at a specific interval. For example, to run the script every daily at 01:00, add the following line to your crontab by running crontab -e:
0 2 * * * cd /path/to/app/task-tracker/ && /usr/bin/npm run daily
You can use a cron job to run the script at a specific interval. For example, to run the script every daily at 02:00, add the following line to your crontab by running crontab -e:
0 3 * * * cd /path/to/app/task-tracker/ && ./backup.sh
For network backups after the local backups, follow the instructions in copy_backups.sh and mount a network drive. You can then schedule the script to run after the local backups using a cron job.
Set the application user in restore_backup.sh to the Linux environment user. Similar to the setup script.
Use the existing backup-[date].tar.gz in the working directory or copy them from another location to the working directory.
Run sudo ./restore_backup.sh to restore the database and files folders. This will use the latest backup file found in the working directory to gracefully restore the database, task attachments and user avatars.
npm install prisma --save-devnpx prisma init --datasource-provider sqlite- Configure
./prisma/schema.prismato setup some models
npx prisma migrate dev --name whatever-changefor every change- If the migrations were pulled from a commit, run
npx prisma migrate deployto apply them npx prisma generateto generate the client
npx prisma studioto open the studio- To generate a password hash to insert in the database, change the
passconst in./lib/hashpass.jsand runnode ./lib/hashpass.jsto get the hash printed to the console
Running setup.sh will create the test folders as well. To run the tests, use npm test. A testing database will be created and destroyed for each test run.
Optional. When enabled, a "Sign in with Microsoft" button appears on the sign-in page, shown above the email/password form as the primary option. It never registers anyone: an admin still creates the user in the FE by email address, and the first Microsoft sign-in links that row to the tenant account.
The email/password form itself is controlled separately by PASSWORD_AUTH_ENABLED (default on). Once
M365 sign-in is rolled out to everyone, set it to false to retire password login: the form disappears
from the page and the sign-in server action refuses direct submissions too, not just the UI.
Because tenant usernames and mailbox addresses often differ (bogdanb@example.com vs
bogdanb-it@example.com), linking matches the app's email against the token's email claim first
and falls back to the username (upn / preferred_username). From then on the user is recognised by
their Entra object id, which survives renames.
A user who was created but never opened their welcome email is activated by their first successful
Microsoft sign-in. A user an admin has deactivated is refused, and can never be revived this way.
Password sign-in keeps working for everyone, linked or not, as long as PASSWORD_AUTH_ENABLED is on.
- Entra admin center -> App registrations -> New registration.
- Supported account types: Accounts in this organizational directory only (single tenant).
- Redirect URI: type Web, value
<BASE_URL>/api/auth/m365/callback. Add a second one for local development, e.g.http://localhost:3000/api/auth/m365/callback.
- Certificates & secrets -> New client secret -> copy the Value (not the Secret ID).
- Token configuration -> Add optional claim -> ID -> tick
emailandupn. Without theemailclaim the app falls back to a Microsoft Graph lookup, which costs an extra request per sign-in. - API permissions:
User.Read(delegated) is the default and is enough. Grant admin consent so nobody is prompted. - Copy the Directory (tenant) ID and Application (client) ID from the Overview page.
Then set in .env.local:
M365_AUTH_ENABLED=true
M365_TENANT_ID="..."
M365_CLIENT_ID="..."
M365_CLIENT_SECRET="..."
PASSWORD_AUTH_ENABLED=true
All four M365 vars must be present. If any is missing the feature stays off rather than failing at
runtime. The redirect URI is built from BASE_URL, so that must match the app registration exactly.
Admins can see whether a user is linked, and unlink them, on the user's page at /users/<id> - useful
when someone's tenant account is recreated and gets a new object id.
Run command pallette Ctrl+Shift+P and search for SFTP: Config to create a new configuration file. Add the following content:
{
"name": "Raspberry Pi",
"host": "",
"protocol": "sftp",
"port": 22,
"username": "",
"password": "",
"remotePath": "/path/to/app/task-tracker",
"uploadOnSave": true
}- 1.0.1 - Added missing users page pagination controls
- 1.0.2 - Fixed new user manager selection
- 1.0.3 - No indexing in robots.txt and metadata
- 1.0.4 - Increased source field character limit to 100 and reduced tasks items per page to 10
- 1.0.5 - Show dates in local time
- 1.0.6 - Fixed Close Task button being async
- 1.0.7 - Fixed
totalDaysWorkingOnTasksuser stat calculation - 1.0.8 - Fixed daily tasks
dueSoonTasksfetching logic - 1.0.9 - Fixed Task History timestamps
- 1.0.10 - Better user logging
- 1.0.11 -
noTasksReviewedClosednot updating - 1.1.0 - Added user stats
- 1.1.1 - Fixed NaN task completion stats
- 1.1.2 - Fixed User stats average times
- 1.1.3 - Fixed average task review time calculation
- 1.2.0 - Task reviewing days changed from Int to Float
- 1.2.1 - Fixed task reviewing days calculation and display
- 1.2.2 - Increased the closing comment character limit from 200 to 1000
- 1.2.3 - If the task review/completion time is 0 days, display it in hours
- 1.2.4 - Fixed forget user password being case sensitive for email
- 1.3.0 - Added task closing comments in the reviewer email
- 1.3.1 - Improved task free text search performance
- 1.3.2 - Replaced
<Link>with<a>for Source Link - 1.3.3 - Fixed bug with deactivating users with assigned but completed tasks
- 1.3.4 - Fixed task submission button allowing multiple submissions by disabling the button while submitting
- 1.3.5 - Fixed task completion and reopen Confirm buttons allowing multiple submissions by disabling the button while submitting
- 1.4.0 - Added task completion comments in the schema for Ready for Review email reminders
- 1.4.1 - Update Next.js to 14.2.25 to fix CVE-2025-29927
- 1.5.0 - Added email outbox and email worker for improved email reliability
- 1.5.1 - Refactored logging functions
- 1.5.2 - Idempotency for emails
- 1.5.3 - Clear the toast URL params after the toast is shown
- 1.6.0 - Display the task ID in the task list and details page and make it searchable
- 1.6.1 - Fixed reading localStorage outside useEffect
- 1.6.2 - Fixed
email-worker.tsdependencies so it can run standalone - 1.7.0 - Updated to Next.js 16 and fixed issues arising from the update
- 1.7.1 - Modified environment variables loading
- 1.7.2 - Fix avatar update db write conflict
- 1.7.3 - Update to NextJS 16.0.7 to mitigate CVE-2025-55182
- 1.7.4 - Fixed task edit page not loading due to incorrect param handling
- 1.7.5 - Update to NextJS 16.0.10 to mitigate CVE-2025-55184 and CVE-2025-55183
- 1.7.6 - Fixed wrong redirect URL after task actions and updated dependencies
- 1.7.7 - Locked Axios version to 1.14.0 and 0.30.3 to avoid the vulnerabilities in 1.14.1 and 0.30.4
- 1.7.8 - Update NextJS to 16.2.4 and other dependencies, and include the dash as an accepted special character for passwords
- 1.8.0 - Added a note in the comments section about using the @ symbol to mention users
- 1.8.1 - Included date in emails idempotency key to allow sending the same email on different days, for example, daily reminders
- 1.8.2 - Updated to NextJS 16.3.0 and other dependencies
- 1.8.3 - Remove
log-to-filedependency and use a custom logger instead - 1.9.0 - Added Markdown support for task descriptions, with a help popover for Markdown syntax
- 1.10.0 - Improve security stance by fixing multiple vulnerabilities:
- Server actions take the acting user from the session, not from form fields like
userIdoreditor - Password reset verifies the token itself instead of a user ID sent with the form, and tokens are single use
- Only admins can grant admin rights, and non-admins can only edit their own account
- Attachment filenames are sanitised and confined to the task's own folder, with a size limit
- Task and attachment actions check the caller may act on that specific task; department actions require admin
- Changing or resetting a password, and deactivating a user, end that user's other sessions
- Excel export is no longer shared between users, and escapes text so it cannot run as a formula
- Added security headers, stopped logging session tokens, and fixed a spoofable rate limiting IP
- Added end-to-end tests for all of the above and for previously untested flows
- Server actions take the acting user from the session, not from form fields like
- 1.11.0 - Converted
dailyTasksto TS - 1.11.1 - Fixed 2 automated tests failing
- 1.12.0 - Task description diff view
- 1.12.1 - Fixed a test
- 1.12.2 - Fixed a bug that allowed the task history window to horizontally grow without limit
- 1.13.0 - Microsoft 365 (Entra ID) sign-in, with optional password retirement and admin unlinking
- 1.14.0 - Dashboard only visible to logged-in users