Skip to content

Latest commit

 

History

132 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kairo

A real-time, multi-user project & task tracker. Go REST + WebSocket API, React/TypeScript frontend, Postgres. Create projects, invite collaborators by email, plan work on a drag-and-drop board, a calendar or a Gantt-style timeline, and see who else is looking at the same project as it happens — live cursors, presence and typing indicators, over a real WebSocket hub, not polling.

Live: kairo.davida.ink

CircleCI License: MIT

Kanban board


Features

Authentication

  • JWT auth (register, login, bcrypt password hashing)
  • Every project/task/comment route checks the caller is a member; mutating a project or its membership requires being the owner

Projects & collaboration

  • Full project CRUD, owned by the creator
  • Owner is added as a project member automatically, in the same DB transaction as project creation
  • Invite collaborators by email — this only works for people who already have a Kairo account; the UI says so up front rather than silently failing

Tasks

  • Full task CRUD, nested under a project (/projects/:id/tasks), with a start_date/due_date range and status tracking (todo / in_progress / done)

  • Drag-and-drop Kanban board (native HTML5 DnD — no extra dependency), color-coded by status, plus a status dropdown as a keyboard/accessible fallback

  • A task can never be read or modified through another project's URL, even by a member of that other project — verified both at the unit-test level and against a live backend

    Timeline

  • Timeline view: tasks rendered as date-scaled Gantt bars, color-coded by status, with an "unscheduled" list for anything missing dates

  • Calendar view: month grid with tasks on their due date, click any day to see everything due and add a new task straight onto it, drag a task to a different day to reschedule

    Calendar

Comments

  • Threaded comments per task (/projects/:id/tasks/:id/comments)
  • Delete authorization: a comment's own author, or the project owner (moderation)

Real-time collaboration

  • A WebSocket per project (/api/projects/:id/ws, backend/internal/realtime) broadcasts presence, cursor position, and typing state to every other connected member
  • Presence: glowing avatars for everyone currently viewing the project, live join/leave
  • Live cursors: colored pointer + name tag tracking collaborators' mouse position on the board in real time
  • Typing indicators: animated dots while a teammate is writing a comment
  • Authenticated the same way the REST API is (JWT), just carried as a ?token= query param since the browser's WebSocket handshake can't set an Authorization header — validated, and project membership checked, before the connection is ever upgraded

Dashboard

  • Live charts over real task/project data: task status breakdown, tasks per project, upcoming deadlines — no fixture data, no fake charts

    Dashboard

Frontend

  • React + TypeScript + Vite + Tailwind + TanStack Query
  • Persistent app shell: sticky top nav, a GitHub-settings-style project sidebar (Board / Timeline / Calendar / Members / Settings), footer
  • Login/register, dashboard, task detail modal (comments + typing indicator), member management UI, project settings (rename/delete) that hide owner-only actions from non-owners

Tech Stack

Backend

  • Go, Gin, PostgreSQL, sqlc (typed queries), golang-migrate (applied automatically on boot — embedded in the binary, no separate migration step for a deploy)
  • JWT auth (golang-jwt), bcrypt password hashing
  • gorilla/websocket for the real-time presence/cursor/typing channel
  • Clean layering: handler → service → repository, one package per domain under backend/internal/
  • Docker + Docker Compose

Frontend

  • React 19, TypeScript, Vite, Tailwind CSS v4
  • TanStack Query for server state, React Router for routing, Axios for HTTP
  • Native WebSocket client (no socket library) for presence/cursors/typing
  • Vitest + React Testing Library

Infra

  • Backend: Render (Docker web service)
  • Database: Neon (serverless Postgres)
  • Frontend: Vercel (static Vite build)
  • CI: CircleCIgo build / go vet / gofmt / go test ./... on every push (see DEPLOY.md for the full deploy writeup)

Getting Started

Prerequisites

  • Go 1.24+
  • Node 20+
  • Docker + Docker Compose

Backend

cd backend
cp .env.example .env      # adjust values if needed - defaults work with docker-compose
docker-compose up --build

This brings up Postgres and the API together, and the API migrates its own schema on boot — no separate migration step needed. The API listens on http://localhost:8080.

Run the backend test suite:

go test ./...

Frontend

cd frontend
npm install
cp .env.example .env.local   # VITE_API_URL should point at the backend above
npm run dev

Open http://localhost:5173. The dev server origin is already allowed by the backend's CORS config (CORS_ALLOWED_ORIGINS in backend/.env) — the same allow-list also gates the WebSocket handshake's Origin header.

Run the frontend test suite:

npm run test

Deploying your own

See DEPLOY.md for the full Render + Neon + Vercel walkthrough.


Architecture

frontend (React/Vite)  --HTTP/JSON-->   backend (Gin)  --sqlc-->  Postgres
                        --WebSocket-->  internal/realtime (per-project hub)

The backend follows a handler → service → repository layering per domain (auth, project, task, comment under backend/internal/). Repositories wrap sqlc-generated queries; only project's repository uses a transaction (creating a project and adding its owner as a member atomically). Two Gin middlewares gate every project/task/comment route: RequireProjectMember and RequireProjectOwner, both driven off the :projectID URL param and the JWT-derived caller ID.

internal/realtime runs one Hub goroutine per project (created lazily, evicted once empty), fanning presence_join / presence_leave / cursor / typing_start / typing_stop messages out to every other connected client. All shared state is only ever touched from inside each hub's own goroutine, driven by channels — no shared-memory locking bugs by construction.

The frontend talks to the backend over REST for everything except live presence, which uses one native WebSocket per project (opened once in ProjectLayout, shared by every page under it via PresenceContext). TanStack Query owns all server state and cache invalidation.


API Reference

All routes are under /api. Auth column: public · requires a JWT · requires project membership · requires project ownership.

Method Path Auth Description
POST /auth/register public Register a new user
POST /auth/login public Log in, get a JWT
GET /protected/profile requires JWT Demo route echoing the caller's claims
POST /projects requires JWT Create a project (caller becomes owner)
GET /projects requires JWT List projects the caller owns or belongs to
GET /projects/:projectID requires project membership Get a project
PATCH /projects/:projectID requires project ownership Update a project's name/description
DELETE /projects/:projectID requires project ownership Delete a project
GET /projects/:projectID/members requires project membership List project members
POST /projects/:projectID/members requires project ownership Add a member by email
DELETE /projects/:projectID/members/:userID requires project ownership Remove a member
POST /projects/:projectID/tasks requires project membership Create a task
GET /projects/:projectID/tasks requires project membership List a project's tasks
GET /projects/:projectID/tasks/:taskID requires project membership Get a task
PATCH /projects/:projectID/tasks/:taskID requires project membership Update a task's fields
PATCH /projects/:projectID/tasks/:taskID/status requires project membership Move a task between todo/in_progress/done
DELETE /projects/:projectID/tasks/:taskID requires project membership Delete a task
POST /projects/:projectID/tasks/:taskID/comments requires project membership Add a comment
GET /projects/:projectID/tasks/:taskID/comments requires project membership List a task's comments
DELETE /projects/:projectID/tasks/:taskID/comments/:commentID requires project membership* Delete a comment (*author or owner only)
GET /projects/:projectID/ws requires project membership** WebSocket: presence, live cursors, typing (**auth via ?token=, not a header)

Roadmap

Deliberately out of scope for now:

  • Labels/tags, task priority, task dependencies
  • Idea-capture flow (raw ideas → promoted into projects/tasks)
  • Real email invites (adding a member by email currently requires them to already have an account — no invite email is sent)
  • OpenAPI/Swagger docs

License

This project is licensed under the MIT License.

About

A modular project collaboration platform API

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages