{"id":17689145,"url":"https://github.com/isnackable/saf-duty","last_synced_at":"2025-08-22T21:32:10.524Z","repository":{"id":184790688,"uuid":"598078124","full_name":"ISnackable/saf-duty","owner":"ISnackable","description":"saf-duty; A Duty Roster planner for the Singapore Armed Forces (SAF). Aims to be a general purpose duty/shifts management app. GPL-3.0 License.","archived":false,"fork":false,"pushed_at":"2025-05-17T07:04:46.000Z","size":32104,"stargazers_count":2,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-17T08:19:27.816Z","etag":null,"topics":["army","military","national-service","singapore"],"latest_commit_sha":null,"homepage":"https://afpn-cdo.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ISnackable.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-02-06T10:56:01.000Z","updated_at":"2025-02-12T11:03:49.000Z","dependencies_parsed_at":"2023-09-30T07:48:08.359Z","dependency_job_id":"e6b6dce5-7580-4d67-92b1-c14f843253d2","html_url":"https://github.com/ISnackable/saf-duty","commit_stats":null,"previous_names":["isnackable/saf-duty"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ISnackable/saf-duty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ISnackable%2Fsaf-duty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ISnackable%2Fsaf-duty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ISnackable%2Fsaf-duty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ISnackable%2Fsaf-duty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ISnackable","download_url":"https://codeload.github.com/ISnackable/saf-duty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ISnackable%2Fsaf-duty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271703772,"owners_count":24806527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["army","military","national-service","singapore"],"created_at":"2024-10-24T11:46:38.801Z","updated_at":"2025-08-22T21:32:06.972Z","avatar_url":"https://github.com/ISnackable.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ISnackable/duty-roster/\"\u003e\n    \u003cimg src=\"./public/icons/icon512_rounded.png\" alt=\"Logo\" width=\"96\" height=\"96\"\u003e\n  \u003c/a\u003e\n\n  \u003ch3 align=\"center\"\u003eSAF Duty Roster\u003c/h3\u003e\n\n  \u003cp align=\"center\"\u003e\n    A Duty Roster planner for the SAF. It's tailored for unit uses, however the code is customizable. GPL-3.0 License.\n    \u003cbr /\u003e\n    \u003ca href=\"https://afpn-cdo.vercel.app/\"\u003e\u003cstrong\u003eView Demo »\u003c/strong\u003e\u003c/a\u003e\n    \u003cbr /\u003e\n\n  \u003c/p\u003e\n\u003c/p\u003e\n\n## About The Project\n\nIn SAF, soldiers are required to do duties, be it guard duty or 24hr ops duty. This project is tailored for unit, where they are required to do 24hr ops duty. The duty roster has these requirements:\n\n1. The duty roster should be spread out in a month of a calendar\n2. It is a 24-hour duty shift\n3. There must be a duty personnel for each day from the start to the end of the month\n4. There should not be any back-to-back duty for the same duty personnel.\n5. There should be an equal number of duties for everyone if possible.\n6. Personnel should be shuffled to avoid the first person always having the most number of duties\n7. Personnel can have block out dates where they should not have duties on the date they have blocked out.\n\n\u003cbr/\u003e\n\n![preview](./images/preview.png)\n\n## Features\n\n- Duty roster generation with points algorithm\n- Blockout dates management for personnel to avoid duties\n- Duty swap request with another personnel\n- Push notifications for reminders of duty and swap requests\n\n## Getting Started\n\nTo get a local copy up and running follow these simple example steps.\n\n### Prerequisites\n\nYou'll first need a Supabase project which can be made [via the Supabase dashboard](https://database.new).\n\n### Clone and run locally\n\n1. Clone this project with Git\n\n   ```bash\n   git clone https://github.com/ISnackable/saf-duty\n   ```\n\n2. Rename `.env.local.example` to `.env.local` and update the following:\n\n   ```\n   NEXT_PUBLIC_SUPABASE_URL=[INSERT SUPABASE PROJECT URL]\n   NEXT_PUBLIC_SUPABASE_ANON_KEY=[INSERT SUPABASE PROJECT API ANON KEY]\n   ```\n\n   Both `SUPABASE_API_URL` and `SUPABASE_ANON_KEY` can be found in [your Supabase project's API settings](https://app.supabase.com/project/_/settings/api) or by running `npx supabase status`.\n\n3. Configure the vault secrets using the database studio SQL editor. Replace the keys with the appropriate values. Take note, `SUPABASE_ROLE_KEY` is different from `SUPABASE_ANON_KEY`. Do not mix them up.\n\n   ```sql\n   insert into vault.secrets (secret, name, description) values ('[INSERT SUPABASE PROJECT URL]', 'project_url'), ('[INSERT SUPABASE ROLE KEY]', 'service_role_key');\n   ```\n\n4. You can now run the Next.js local development server:\n\n   ```bash\n   pnpm dev\n   ```\n\n   The app should now be running on [localhost:3000](http://localhost:3000/).\n\n## Configuration\n\nAs much as this project aims to set up as seamlessly as possible, due to some limitation to Supabase, there are some config that has to be done manually.\n\n### Create vault secrets (Required)\n\nNavigate to [Project Vault settings](https://supabase.com/dashboard/project/_/settings/vault/secrets) in your Supabase Dashboard or.\n\n1. Create a new secret with the name `project_url`.\n1. The secret value should be your project's url, you can find the it through the [Project API settings](https://supabase.com/dashboard/project/_/settings/api).\n1. Repeat for the secret `service_role_key`.\n\n### Push Notification (Optional)\n\nTo Enable Push Notification with [Web Push](https://web.dev/articles/push-notifications-web-push-protocol). You will need to deploy the Supabase Edge Function and create the database webhook manually.\n\n#### 1. Deploy the Supabase Edge Function\n\nThe database webhook handler to send push notifications is located in `supabase/functions/push/index.ts`. Deploy the function to your linked project and set the `WEB_PUSH_PUBLIC_KEY`, `WEB_PUSH_PRIVATE_KEY` \u0026 `WEB_PUSH_EMAIL` secret.\n\n1. `supabase functions deploy push`\n2. `supabase secrets set --env-file .env.local`\n\n_Note: The Public and Private keys secrets must be generated using the [alastaircoote/webpush-webcrypto](https://github.com/alastaircoote/webpush-webcrypto/) package._\n\n#### 2. Create the database webhook\n\nNavigate to the [Database Webhooks settings](https://supabase.com/dashboard/project/_/database/hooks) in your Supabase Dashboard.\n\n1. Enable and create a new hook with the name `on_after_notifications_created`.\n1. Conditions to fire webhook: Select the `notifications` table and tick the `Insert` event.\n1. Webhook configuration: Supabase Edge Functions.\n1. Edge Function: Select the `push` edge function and leave the method as `POST` and timeout as `1000`.\n1. HTTP Headers: Click \"Add new header\" \u003e \"Add auth header with service key\" and leave Content-type: `application/json`.\n1. Click \"Create webhook\".\n\n#### 3. Sending a push notification\n\nWhen a new row is added in your notifications table, a push notification will be sent to the user who has subscribed to push notification.\n\n- _Note: There's also a cron job for [daily duty reminder](./supabase/migrations/20240130130803_enable_extensions.sql) if a user is subscribed to push notification._\n\n  ```sql\n   -- View all scheduled job.\n   select * from cron.job;\n\n   -- Or unschedule it if you want to disable it.\n   select cron.unschedule('daily-roster-reminder');\n  ```\n\n## Contributing\n\nIf you have a suggestion that would make this better, please fork the repo and create a pull request. Any contributions you make are greatly appreciated.\n\nI recommend you to use [GitHub Codespaces](https://github.com/features/codespaces) for ease of development. Below lists the steps on how you can get started.\n\n1. Fork the Project\n\n2. Create a GitHub Codespace instance\u003cbr/\u003e\n   \u003cimg src=\"https://docs.github.com/assets/cb-49943/images/help/codespaces/who-will-pay.png\" alt=\"How to create a GitHub Codespaces\" width=\"250\"/\u003e\n\n3. Start the local database (If it hasn't already been started already)\n\n   ```sh\n   npx supabase start\n   ```\n\n   The database should now be running and the supabase studio is at [localhost:54323](http://localhost:54323/).\n\n4. Follow this [section](#clone-and-run-locally) starting from part 2 onwards.\n\n## License\n\nDistributed under the **GNU General Public License v3.0** License. See `LICENSE` for more information.\n\n## Acknowledgements\n\n- [supabase | Postgres Database \u0026 Authentication](https://supabase.com/)\n- [shadcn/ui | React Component Library](https://ui.shadcn.com/)\n- [unDraw](https://undraw.co/license)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisnackable%2Fsaf-duty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fisnackable%2Fsaf-duty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisnackable%2Fsaf-duty/lists"}