feat: basic backend #158

Merged
addison merged 51 commits from 11-backend into main 2026-07-25 12:49:09 -04:00
Collaborator

Description

Backend implementation as per the spec #11 (backend.md)

Testing Checklist

This testing can be fully automated using an LLM and cURL , this list is merely a checklist of the possibilities covered by the changes in this branch.

Setup

  • Server running with JWT_SECRET set (no SMTP → OTP codes appear in the server log).
  • One sys-admin seeded out-of-band: karriba-seed -email [email protected].
  • Base URL: http://localhost:8080. All responses are JSON; errors use {"error":{"code","message"}}.

1. Health & Auth

  • GET /v1/health200 {"status":"ok","version":...} (no auth)
  • POST /v1/auth/otp/request (registered contact) → 200, expires_in:900; code appears in log
  • POST /v1/auth/otp/request (unregistered contact) → identical 200 (no code generated)
  • POST /v1/auth/otp/verify with the logged code → 200 with access_token, refresh_token, correct user payload
  • POST /v1/auth/otp/verify wrong code → 400 INVALID_OTP
  • POST /v1/auth/refresh with the returned token → 200 with a new refresh_token; reusing the old one → 401 REVOKED_TOKEN

2. Access Control

  • Any protected endpoint with no / invalid token → 401
  • Sys-admin token on a manager-only endpoint (list users) → 403 INSUFFICIENT_ROLE
  • Member accessing another org → 403

3. Orgs (sys-admin token)

  • POST /v1/orgs201; initial_manager.role:"manager"; a tenant DB file for the new org id is created
  • GET /v1/orgs/:id200 with user_count
  • PATCH /v1/orgs/:id rename → 200; archive, then confirm members get 403 while sys-admin still reads it

4. Users (manager token)

  • GET /v1/orgs/:id/users200
  • POST /v1/orgs/:id/users invite member → 201
  • Invite with a bad role (e.g. "supervisor") → 400 INVALID_ROLE
  • Demote the only manager → 400 ORG_REQUIRES_MANAGER
  • Member self-leave DELETE .../users/:selfId204

5. Pagination

  • GET /v1/orgs/:id/users?limit=1 → 1 item + non-null next_cursor
  • Re-request with ?limit=1&cursor=<value> → next page; last page has next_cursor:null

6. Entitlements

  • GET /v1/orgs/:id/entitlements (any member) → 200
  • PUT /v1/orgs/:id/entitlements (sys-admin) ["pesticide","cloud_sync"]200
  • PUT ["cloud_sync"] alone → 400 ENHANCEMENT_REQUIRES_STANDALONE (with module/requires)
  • PUT unknown module → 400 INVALID_MODULE
  • PUT as manager/member → 403

7. Rate Limiting (needs a proxy-free direct hit)

  • Fire >30 auth requests from one IP within a minute → excess → 429 RATE_LIMITED
  • Confirm normal traffic recovers after the window

Notes

  • Timestamps are RFC 3339 UTC; IDs are UUIDv7.
  • Automated coverage exists for: OTP lockout/expiry, refresh reuse family-revocation, all role guards, invalid module/role codes, and the entitlement→tenant-DB sync.
# Description Backend implementation as per the spec #11 (backend.md) # Testing Checklist This testing can be fully automated using an LLM and cURL , this list is merely a checklist of the possibilities covered by the changes in this branch. ## Setup - Server running with `JWT_SECRET` set (no SMTP → OTP codes appear in the server log). - One sys-admin seeded out-of-band: `karriba-seed -email [email protected]`. - Base URL: `http://localhost:8080`. All responses are JSON; errors use `{"error":{"code","message"}}`. ## 1. Health & Auth - [x] `GET /v1/health` → `200 {"status":"ok","version":...}` (no auth) - [x] `POST /v1/auth/otp/request` (registered contact) → `200`, `expires_in:900`; code appears in log - [x] `POST /v1/auth/otp/request` (unregistered contact) → identical `200` (no code generated) - [x] `POST /v1/auth/otp/verify` with the logged code → `200` with `access_token`, `refresh_token`, correct `user` payload - [x] `POST /v1/auth/otp/verify` wrong code → `400 INVALID_OTP` - [x] `POST /v1/auth/refresh` with the returned token → `200` with a **new** `refresh_token`; reusing the old one → `401 REVOKED_TOKEN` ## 2. Access Control - [x] Any protected endpoint with no / invalid token → `401` - [x] Sys-admin token on a manager-only endpoint (list users) → `403 INSUFFICIENT_ROLE` - [x] Member accessing another org → `403` ## 3. Orgs (sys-admin token) - [x] `POST /v1/orgs` → `201`; `initial_manager.role:"manager"`; a tenant DB file for the new org id is created - [x] `GET /v1/orgs/:id` → `200` with `user_count` - [x] `PATCH /v1/orgs/:id` rename → `200`; archive, then confirm members get `403` while sys-admin still reads it ## 4. Users (manager token) - [x] `GET /v1/orgs/:id/users` → `200` - [x] `POST /v1/orgs/:id/users` invite member → `201` - [x] Invite with a bad role (e.g. `"supervisor"`) → `400 INVALID_ROLE` - [x] Demote the only manager → `400 ORG_REQUIRES_MANAGER` - [x] Member self-leave `DELETE .../users/:selfId` → `204` ## 5. Pagination - [x] `GET /v1/orgs/:id/users?limit=1` → 1 item + non-null `next_cursor` - [x] Re-request with `?limit=1&cursor=<value>` → next page; last page has `next_cursor:null` ## 6. Entitlements - [x] `GET /v1/orgs/:id/entitlements` (any member) → `200` - [x] `PUT /v1/orgs/:id/entitlements` (sys-admin) `["pesticide","cloud_sync"]` → `200` - [x] `PUT` `["cloud_sync"]` alone → `400 ENHANCEMENT_REQUIRES_STANDALONE` (with `module`/`requires`) - [x] `PUT` unknown module → `400 INVALID_MODULE` - [x] `PUT` as manager/member → `403` ## 7. Rate Limiting (needs a proxy-free direct hit) - [x] Fire >30 auth requests from one IP within a minute → excess → `429 RATE_LIMITED` - [x] Confirm normal traffic recovers after the window ## Notes - Timestamps are RFC 3339 UTC; IDs are UUIDv7. - Automated coverage exists for: OTP lockout/expiry, refresh reuse family-revocation, all role guards, invalid module/role codes, and the entitlement→tenant-DB sync.
julian self-assigned this 2026-04-05 10:16:37 -04:00
Owner

Be sure also to hook up the CI/CD to run go tests

Be sure also to hook up the CI/CD to run go tests
test(backend): add further coverage
All checks were successful
Backend CI / test (pull_request) Successful in 22s
bde5e86e44
julian changed title from WIP: feature: backend to feature: backend 2026-04-26 20:18:52 -04:00
julian changed target branch from infrastructure-backend-design to main 2026-07-12 22:22:24 -04:00
Merge remote-tracking branch 'origin' into 11-backend
All checks were successful
Backend CI / test (pull_request) Successful in 1m47s
06f3b2379e

📦 Build Successful!

Commit: bc36d27
Size: 59M

Download APK

## 📦 Build Successful! **Commit:** `bc36d27` **Size:** 59M [Download APK](https://git.kwila.cloud/kwila/karriba/actions/runs/1398/artifacts/karriba-apk)
refactor(backend): cahnge sql drive to mattn CGO library
Some checks failed
Backend CI / test (pull_request) Failing after 1m15s
ae5cdbff4d
refactor(ci): possible fix for test ci
All checks were successful
Backend CI / test (pull_request) Successful in 1m30s
0b00d86c56
julian requested review from addison 2026-07-12 22:58:12 -04:00
Author
Collaborator

This should be ready for review 🫡This should be one of the last BIG changes 😅

This should be ready for review 🫡This should be one of the last BIG changes 😅
Owner

Great! I plan to review this week, Thursday at the latest.

Great! I plan to review this week, Thursday at the latest.
Owner

All the stuff mentioned in the MR description also has automated tests to run in CI?

All the stuff mentioned in the MR description also has automated tests to run in CI?
Author
Collaborator

@addison wrote in #158 (comment):

All the stuff mentioned in the MR description also has automated tests to run in CI?

I think most of it should be covered. There may be some gap I'm unaware of.

@addison wrote in https://git.kwila.cloud/kwila/karriba/pulls/158#issuecomment-3046: > All the stuff mentioned in the MR description also has automated tests to run in CI? I think most of it should be covered. There may be some gap I'm unaware of.
@ -0,0 +4,4 @@
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o karriba-server ./cmd/karriba-server
Owner

We need to enable cgo, right? Otherwise everything is going to fail inside this image.

We need to enable cgo, right? Otherwise everything is going to fail inside this image.
addison marked this conversation as resolved
test: prove refresh bug
Some checks failed
Backend CI / test (pull_request) Failing after 1m14s
b5c1f922e3
Owner

b5c1f922e3 adds test case to show bug in refresh handling

b5c1f922e3 adds test case to show bug in refresh handling
test(auth): prove concurrent OTP consumption
Some checks failed
Backend CI / test (pull_request) Failing after 1m15s
3b7fb60fba
Owner

3b7fb60 has a test for concurrent OTP bug

3b7fb60 has a test for concurrent OTP bug
test(org): align archived access with spec
Some checks failed
Backend CI / test (pull_request) Failing after 1m15s
1d8a1c0f88
Owner

1d8a1c0 has a fix for the test - members of archived organizations need read access to the orgs endpoint so they know that their organization is archived - see line 671 of spec 11

1d8a1c0 has a fix for the test - members of archived organizations need read access to the orgs endpoint so they know that their organization is archived - see line 671 of spec 11
test(entitlements): reject tenant sync false success
Some checks failed
Backend CI / test (pull_request) Failing after 1m13s
5f28d53704
Owner

5f28d53 has a test for bug with entitlements handling

5f28d53 has a test for bug with entitlements handling
addison requested changes 2026-07-13 18:04:42 -04:00
Dismissed
addison left a comment

I used codex to help me find a few concerns, and added test cases to show the different situations.

It would also be good to include a CI job to build the docker image to make sure it is fine in every PR.

I used codex to help me find a few concerns, and added test cases to show the different situations. It would also be good to include a CI job to build the docker image to make sure it is fine in every PR.
chore(ci): add build job
All checks were successful
Backend CI / build (pull_request) Successful in 1m21s
Backend CI / test (pull_request) Successful in 1m55s
f102274670
Author
Collaborator

Addressed the test concerns and added the build CI job 😄

Addressed the test concerns and added the build CI job 😄
addison changed title from feature: backend to feat: basic backend 2026-07-14 13:53:12 -04:00
Owner

This one might take a few review iterations since it is so big 😅

This one might take a few review iterations since it is so big 😅
@ -0,0 +1,9 @@
-- Tenant DB schema mirrors the Flutter app's Drift schema.
-- This is a placeholder; the full schema should be kept in sync
Owner

When do we replace the placeholder? In a separate PR or in the cloud sync spec?

When do we replace the placeholder? In a separate PR or in the cloud sync spec?
Author
Collaborator

We could make it in a separate PR to avoid making adding much more to this one since it is already this big😅

We could make it in a separate PR to avoid making adding much more to this one since it is already this big😅
Owner

Yeah, I agree. Especially if we pivot to a separate sync project with an event-log approach.

Yeah, I agree. Especially if we pivot to a separate sync project with an event-log approach.
addison marked this conversation as resolved
test(store): prove tenant connection eviction race
Some checks failed
Backend CI / test (pull_request) Failing after 1m49s
Backend CI / build (pull_request) Successful in 1m23s
6abf284af5
test: prove concurrent refresh bug
Some checks failed
Backend CI / build (pull_request) Successful in 1m13s
Backend CI / test (pull_request) Failing after 1m39s
86045678a3
Owner

A few more tests for a few more broken edge cases. Auth systems are tricky 😅

A few more tests for a few more broken edge cases. Auth systems are tricky 😅
test(store): prove concurrent OTP creation race
Some checks failed
Backend CI / build (pull_request) Successful in 1m13s
Backend CI / test (pull_request) Failing after 1m39s
4f81fe6dd4
addison changed title from feat: basic backend to WIP: feat: basic backend 2026-07-15 08:52:47 -04:00
Merge branch 'main' into 11-backend
All checks were successful
Backend CI / test (pull_request) Successful in 2m6s
Backend CI / build (pull_request) Successful in 5s
49e48291ee

📦 Build Successful!

Commit: 9931082
Size: 62M

Download APK

## 📦 Build Successful! **Commit:** `9931082` **Size:** 62M [Download APK](https://git.kwila.cloud/kwila/karriba/actions/runs/1611/artifacts/karriba-apk)
julian changed title from WIP: feat: basic backend to feat: basic backend 2026-07-23 23:47:02 -04:00
fix(backend): address review follow-ups
All checks were successful
Backend CI / build (pull_request) Successful in 1m6s
Backend CI / test (pull_request) Successful in 2m24s
3cc2cf8468
test(backend): expand API coverage
All checks were successful
Backend CI / build (pull_request) Successful in 1m13s
Backend CI / test (pull_request) Successful in 1m52s
97dd1ff66c
addison approved these changes 2026-07-25 12:49:01 -04:00
addison left a comment

Great foundation!

Great foundation!
addison referenced this pull request from a commit 2026-07-25 12:49:11 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kwila/karriba!158
No description provided.