Skip to content

Mournweiss/task-tracker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Task Tracker

Service for tracking work tasks, part of a Medical Information System (MIS).

Docker Podman Ruby Rails PostgreSQL Hotwire License: Apache 2.0

Overview

A Rails service of Medical Information System (MIS) that enables medical staff to create, manage, and track recurring and one-time tasks.

Task Tracker Overview

Technology Stack:

  • Ruby 3.4+ — Main Programming language
  • Rails 8.1 — Server-side web framework with MVC pattern
  • PostgreSQL 16+ — Relational database with JSONB support
  • Hotwire (Turbo + Stimulus) — Frontend-part
  • Propshaft — Default static asset pipeline in Rails 8
  • Docker/Podman + compose — Containerization and service orchestration

Architecture

graph TD
    subgraph "Client"
        Browser[Web Browser]
    end

    subgraph "tracker-service"
        API[API Controller<br/>JSON]
        Web[Web Controller<br/>Hotwire]
        Domains[Domain Services<br/>DDD Layer]
    end

    subgraph "Database"
        DB[(PostgreSQL 16)]
    end

    Browser --> API
    Browser --> Web
    API --> Domains
    Web --> Domains
    Domains --> DB
Loading

Services

Service Image Port Description
tracker-service ruby:3.4.7 10078 Rails 8.1 task service
db-service postgres:16.13 5432 PostgreSQL database

Usage

  1. Clone the repository:

    git clone https://github.com/Mournweiss/task-tracker.git
    cd task-tracker
  2. Prepare the environment and build:

    chmod +x build.sh
    ./build.sh

    Arguments:

    --podman|-p              Use podman-compose instead of docker-compose
    --docker|-d              Use docker-compose instead of podman-compose
    --foreground|-f          Run in foreground mode
    --help|-h                Show help message
    

    Note: build.sh automatically generates .env and selects an available orchestration engine if no specific option is given. To force a specific orchestrator, use the --podman/-p or --docker/-d argument as needed.

  3. Access the application at http://localhost:10078.

Endpoints

All API endpoints follow JSON:API conventions. Base URL: http://localhost:10078/api/v1. Full API specification is available in OpenAPI docs.

Health Check

Method Path Description
GET /api/v1/health Service health status

Request:

curl http://localhost:10078/api/v1/health

Response (200 OK):

{
    "status": "healthy",
    "timestamp": "2026-05-19T01:00:00Z",
    "checks": {
        "database": {
            "healthy": true,
            "message": "connected"
        }
    }
}

Response (503 Service Unavailable):

{
    "status": "unhealthy",
    "timestamp": "2026-05-19T01:00:00Z",
    "checks": {
        "database": {
            "healthy": false,
            "message": "connection refused"
        }
    }
}

Tasks

Method Path Description
GET /api/v1/tasks List tasks (filters: status, from_date, to_date, tag_ids)
POST /api/v1/tasks Create a task
GET /api/v1/tasks/:id Get task by ID
PATCH /api/v1/tasks/:id Update a task
DELETE /api/v1/tasks/:id Delete a task
GET /api/v1/tasks/calendar Get calendar data for date range

List Tasks — Request:

curl "http://localhost:10078/api/v1/tasks?status=new&from_date=2026-05-01&to_date=2026-05-31&tag_ids=1,2"

List Tasks — Response (200 OK):

{
    "data": [
        {
            "id": 1,
            "type": "tasks",
            "attributes": {
                "title": "Daily Patient Round",
                "description": "Conward rounds on ward 3",
                "scheduled_at": "2026-05-19T09:00:00Z",
                "status": "new",
                "recurrence_type": "daily",
                "recurrence_step": 1,
                "recurrence_day": null,
                "recurrence_dates": null,
                "recurrence_parity": null,
                "end_at": null,
                "created_at": "2026-05-18T12:00:00Z",
                "updated_at": "2026-05-18T12:00:00Z"
            },
            "relationships": {
                "tags": {
                    "data": [{ "id": 1, "type": "tags" }]
                },
                "task_instances": {
                    "data": []
                }
            }
        }
    ]
}

Create Task — Request:

curl -X POST http://localhost:10078/api/v1/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "task": {
      "title": "Prepare Monthly Report",
      "description": "Compile statistics for the department",
      "scheduled_at": "2026-06-01T10:00:00Z",
      "status": "new",
      "recurrence_type": "monthly",
      "recurrence_day": 1,
      "end_at": "2026-12-31T23:59:59Z"
    },
    "tag_ids": [1, 3]
  }'

Create Task — Response (201 Created):

{
    "data": {
        "id": 2,
        "type": "tasks",
        "attributes": {
            "title": "Prepare Monthly Report",
            "description": "Compile statistics for the department",
            "scheduled_at": "2026-06-01T10:00:00Z",
            "status": "new",
            "recurrence_type": "monthly",
            "recurrence_step": null,
            "recurrence_day": 1,
            "recurrence_dates": null,
            "recurrence_parity": null,
            "end_at": "2026-12-31T23:59:59Z",
            "created_at": "2026-05-19T01:00:00Z",
            "updated_at": "2026-05-19T01:00:00Z"
        },
        "relationships": {
            "tags": {
                "data": [
                    { "id": 1, "type": "tags" },
                    { "id": 3, "type": "tags" }
                ]
            },
            "task_instances": {
                "data": []
            }
        }
    }
}

Get Task — Response (200 OK):

curl http://localhost:10078/api/v1/tasks/1
{
    "data": {
        "id": 1,
        "type": "tasks",
        "attributes": {
            "title": "Daily Patient Round",
            "description": "Conward rounds on ward 3",
            "scheduled_at": "2026-05-19T09:00:00Z",
            "status": "new",
            "recurrence_type": "daily",
            "recurrence_step": 1,
            "recurrence_day": null,
            "recurrence_dates": null,
            "recurrence_parity": null,
            "end_at": null,
            "created_at": "2026-05-18T12:00:00Z",
            "updated_at": "2026-05-18T12:00:00Z"
        },
        "relationships": {
            "tags": {
                "data": [{ "id": 1, "type": "tags" }]
            },
            "task_instances": {
                "data": []
            }
        }
    }
}

Update Task — Request:

curl -X PATCH http://localhost:10078/api/v1/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{
    "task": {
      "status": "in_progress",
      "recurrence_step": 2
    }
  }'

Delete Task — Response (204 No Content):

curl -X DELETE http://localhost:10078/api/v1/tasks/1

Calendar — Request:

curl "http://localhost:10078/api/v1/tasks/calendar?from_date=2026-05-19&to_date=2026-05-26"

Calendar — Response (200 OK):

[
    {
        "task_id": 1,
        "task_title": "Daily Patient Round",
        "recurrence_type": "daily",
        "instances": [
            {
                "date": "2026-05-19",
                "task_id": 1,
                "task_title": "Daily Patient Round",
                "status": "new",
                "notes": null,
                "is_virtual": true
            },
            {
                "date": "2026-05-20",
                "task_id": 1,
                "task_title": "Daily Patient Round",
                "status": "new",
                "notes": null,
                "is_virtual": true
            }
        ]
    }
]

Task Instances

Method Path Description
GET /api/v1/tasks/:task_id/instances List instances for a task
POST /api/v1/tasks/:task_id/instances?occurred_at=YYYY-MM-DD Create/update instance
PATCH /api/v1/tasks/:task_id/instances?occurred_at=YYYY-MM-DD Update instance status

List Instances — Response (200 OK):

curl http://localhost:10078/api/v1/tasks/1/instances?from_date=2026-05-01&to_date=2026-05-31
{
    "data": [
        {
            "id": 1,
            "type": "task_instances",
            "attributes": {
                "task_id": 1,
                "occurred_at": "2026-05-19",
                "status": "completed",
                "notes": "Patient rounds completed",
                "created_at": "2026-05-19T11:00:00Z",
                "updated_at": "2026-05-19T14:00:00Z"
            }
        }
    ]
}

Update Instance — Request:

curl -X PATCH "http://localhost:10078/api/v1/tasks/1/instances?occurred_at=2026-05-20" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "completed",
    "notes": "Rounds completed on time"
  }'

Tags

Method Path Description
GET /api/v1/tags List all tags
GET /api/v1/tags/search?q=string Search tags (autocomplete)
POST /api/v1/tags Create a tag
DELETE /api/v1/tags/:id Delete a tag
DELETE /api/v1/tasks/:task_id/tags/:tag_id Unassign tag from task

List Tags — Response (200 OK):

curl http://localhost:10078/api/v1/tags
{
    "data": [
        {
            "id": 1,
            "type": "tags",
            "attributes": {
                "name": "reporting",
                "system": true,
                "created_at": "2026-05-01T00:00:00Z"
            }
        },
        {
            "id": 2,
            "type": "tags",
            "attributes": {
                "name": "operations",
                "system": true,
                "created_at": "2026-05-01T00:00:00Z"
            }
        }
    ]
}

Search Tags — Response (200 OK):

curl "http://localhost:10078/api/v1/tags/search?q=report"
{
    "data": [
        { "id": 1, "name": "reporting", "system": true },
        { "id": 4, "name": "reporting-urgent", "system": false }
    ]
}

Create Tag — Request:

curl -X POST http://localhost:10078/api/v1/tags \
  -H "Content-Type: application/json" \
  -d '{
    "name": "follow-up",
    "system": false
  }'

Create Tag — Response (201 Created):

{
    "data": {
        "id": 5,
        "type": "tags",
        "attributes": {
            "name": "follow-up",
            "system": false,
            "created_at": "2026-05-19T01:00:00Z"
        }
    }
}

Unassign Tag — Response (204 No Content):

curl -X DELETE http://localhost:10078/api/v1/tasks/1/tags/2

Environment Variables

Application

  • SECRET_KEY_BASE: Secret key for session signing (Default: generated)
  • RAILS_ALLOWED_HOSTS: Allowed connection hosts (Default: *)
  • RAILS_ENV: Rails environment (Default: production)
  • RAILS_LOG_TO_STDOUT: Log output to stdout (Default: true)
  • RAILS_SERVE_STATIC_FILES: Serve static files (Default: true)
  • LOG_LEVEL: Logging level (Default: info)
  • MAX_LOG_SIZE: Maximum log file size in bytes (Default: 10485760)

Database

  • DB_HOST: Database host (Default: db-service)
  • DB_PORT: Database port (Default: 5432)
  • DB_NAME: Database name (Default: task_tracker)
  • DB_USER: Database user (Default: postgres)
  • DB_PASSWORD: Database password (Default: postgres)

Container

  • REGISTRY_TRACKER: Ruby image registry (Default: docker.io/library/ruby)
  • VERSION_TRACKER: Ruby image version (Default: 3.4.7)
  • REGISTRY_DB: PostgreSQL image registry (Default: docker.io/library/postgres)
  • VERSION_DB: PostgreSQL image version (Default: 16.13)
  • PROJECT_NAME: Container name prefix (Default: task-tracker)
  • TRACKER_PORT: External port for tracker-service (Default: 10078)

Architectural solutions

1. Basic Task CRUD

The project implements full CRUD operations for tasks via the API:

  • Create: POST /api/v1/tasks — creates a task with title, description, scheduled_at, status, and optional recurrence settings
  • Read: GET /api/v1/tasks (list with filters) and GET /api/v1/tasks/:id (single task)
  • Update: PATCH /api/v1/tasks/:id — updates task attributes including status transitions
  • Delete: DELETE /api/v1/tasks/:id — deletes task with cascade removal of instances and tags

Each task has at minimum: title, description, scheduled_at, and status fields.

2. Tags for Tasks

Many-to-many tag relationships are implemented via the task_tags join table:

  • Assign: Pass tag_ids array when creating/updating a task
  • Unassign: DELETE /api/v1/tasks/:task_id/tags/:tag_id
  • Search: GET /api/v1/tags/search?q=string for autocomplete

System tags (reporting, operations, call) are:

3. Recurrence Settings

The RecurrenceCalculator supports all required recurrence types:

Type Field Description
daily recurrence_step Every N days
monthly recurrence_day On specific day (1-31)
specific_dates recurrence_dates (JSONB) On explicit dates
even_odd recurrence_parity Even/odd days only

4. Infinity Problem Solution

Infinite recurring tasks (without end_at) are handled via lazy computation:

  • The tasks table stores only the rule (recurrence configuration), not pre-generated instances
  • Instances are computed on-the-fly when requesting calendar data via CalendarService
  • The RecurrenceCalculator expands only instances within the requested [from_date, to_date] window
  • Maximum computation window is bounded to prevent excessive processing

5. Instance State Independence

Each instance has its own independent status:

  • The task_instances table stores overrides (status changes, notes, cancellations)
  • Unmodified instances are virtual — they inherit status from the parent task
  • effective_status method returns the instance's own status or inherits from parent
  • Marking one instance as completed does not affect other instances

6. Instance Exceptions

Instances can be modified independently from the parent task rule:

  • status field in task_instances allows overriding the inherited status
  • notes field stores instance-level notes
  • PATCH /api/v1/tasks/:task_id/instances?occurred_at=YYYY-MM-DD updates an instance
  • An instance with a non-null status is considered an "exception" from the rule

License

This project is licensed under the Apache License 2.0.

Component Licenses

Component License Source
Ruby Ruby License ruby/ruby
Rails MIT rails/rails
PostgreSQL PostgreSQL License postgres/postgres
Turbo MIT hotwired/turbo
Stimulus MIT hotwired/stimulus
Propshaft MIT rails/propshaft
jsonapi-serializer MIT jsonapi-serializer/jsonapi-serializer
Puma BSD License puma/puma
bootsnap MIT License Shopify/bootsnap
RuboCop MIT rubocop/rubocop
Docker Apache 2.0 docker/docker

About

Service for tracking work tasks, part of a Medical Information System (MIS)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors