Commit Graph

2507 Commits

Author SHA1 Message Date
Frederick [Bot] 89ee1ef507 [skip ci] Updated swagger docs 2026-06-11 20:50:04 +00:00
kolaente e5055d720c test(api/v2): split the B1 webtests into per-route files
Replace huma_backgrounds_misc_test.go with huma_background_test.go,
huma_info_test.go, huma_webhook_event_test.go and huma_user_search_test.go so
each route area's tests live in their own file.
2026-06-11 20:07:43 +00:00
kolaente 5807f2e7b4 refactor(user): share user-search logic between v1 and v2
Extract the duplicated user-search business logic into two helpers both API
versions call, and refactor v1's handlers onto them:
- user.SearchUsers wraps ListUsers + email obfuscation (global search)
- models.SearchUsersForProject wraps the project read check + ListUsersFromProject

Each handler keeps its own forbidden mapping (v1 echo.ErrForbidden vs v2
huma) so v1 stays byte-identical on the wire.
2026-06-11 20:07:43 +00:00
kolaente 5dcc501d54 feat(api/v2): add user search endpoints
Port to /api/v2:
- GET /users (global user search by username/name/email; emails are blanked)
- GET /projects/{project}/users/search (users with access to a project, for
  share autocomplete; requires project read access)

Both are custom routes: the project search loads the project and enforces
CanRead explicitly.
2026-06-11 20:07:43 +00:00
kolaente 3312716afd feat(api/v2): add available webhook events endpoint
Add GET /api/v2/webhooks/events, listing the events a webhook target can
subscribe to. Gated on webhooks.enabled via a registrar early-return, mirroring
v1.
2026-06-11 20:07:43 +00:00
kolaente 56b1ba47ec feat(api/v2): add public instance info endpoint
Add GET /api/v2/info (public — no auth). Extract the /info response type and
its assembly out of the v1 handler into pkg/routes/api/shared.BuildInfo() so
both API versions return byte-identical info; refactor v1's handler onto it.
Add the v2 path to unauthenticatedAPIPaths.
2026-06-11 20:07:43 +00:00
kolaente 6f3dab53cb feat(api/v2): add project background endpoints
Port to /api/v2:
- DELETE /projects/{project}/background (remove background, returns the updated project)
- GET /backgrounds/unsplash/search (q, page; gated on the unsplash provider)
- PUT /projects/{project}/backgrounds/unsplash (set, gated on the unsplash provider)

Custom routes load the project and enforce CanUpdate explicitly. Backgrounds
are gated on the static backgrounds config via a registrar early-return.
Tag background.Image fields with doc: for the v2 schema, and add a scoped
contextcheck exclusion since the unsplash provider's shared interface bottoms
out in context.Background().
2026-06-11 20:07:43 +00:00
Frederick [Bot] ea0c9fbe94 [skip ci] Updated swagger docs 2026-06-11 20:24:56 +00:00
kolaente 53d1fa0735 refactor(admin): share user-mutation logic between v1 and v2
The admin set-admin-flag, set-status and delete-user operations were
implemented twice — once in the v1 echo handlers, once in the v2 Huma handlers.
Extract the load/guard/mutate logic into models.SetUserAdminFlag,
models.SetUserStatusAsAdmin and models.DeleteUserAsAdmin so both APIs call the
same code; each handler keeps only its own request binding, validation and
response shape. v1 stays byte-identical on the wire.
2026-06-11 19:32:42 +00:00
kolaente 5b3ee89edd refactor(api/v2): dedup the admin user-mutation handlers
The patch-admin, patch-status and delete-user handlers each repeated the same
session open/load/commit/rollback scaffold. Extract it into adminMutateUser,
which owns the transaction and takes a closure for each handler's distinct
guard-and-write step.
2026-06-11 19:32:42 +00:00
kolaente 5579daa452 feat(api/v2): add admin actions on /api/v2
Port the admin action endpoints to the Huma-backed /api/v2:

- GET    /admin/overview            instance counts + license snapshot
- POST   /admin/users               create a user (201)
- PATCH  /admin/users/{id}/admin    promote/demote (*bool, nil = unchanged)
- PATCH  /admin/users/{id}/status   set status (*Status, nil = unchanged)
- DELETE /admin/users/{id}          delete (mode=now|scheduled, 204)
- PATCH  /admin/projects/{id}/owner reassign project owner

All sit behind the existing gateV2AdminRoutes path middleware (admin + license
gate, 404 on failure), so no per-handler permission checks are added. The
hand-registered PATCH routes carry genuine partial semantics, which AutoPatch
does not synthesise. The admin user response reuses the existing
pkg/routes/api/shared package.
2026-06-11 19:32:42 +00:00
kolaente e25f997281 refactor(admin): extract shared admin overview, user-create and user-view helpers
Move the admin overview computation and struct into models.BuildOverview /
models.Overview, the admin create-user flow into models.CreateUserAsAdmin /
models.CreateUserBody, and the admin user response view into a new
pkg/routes/api/shared package (shared.AdminUser / shared.NewAdminUser) so both
the v1 and v2 admin routes call the same code. The v1 handlers are refactored
onto these helpers and stay byte-identical on the wire.
2026-06-11 19:32:42 +00:00
kolaente 9c3c1047ac feat(api/v2): port OAuth migrators (Todoist, Trello, Microsoft To-Do)
Add /api/v2 auth/status/migrate endpoints for the three OAuth-based
migrators. One generic helper registers all three ops per migrator
behind its static config gate, so there's no copy-pasted block per
migrator.

The migrate kick-off orchestration (already-running guard + event
dispatch) is extracted into migrationHandler.StartMigration so v1 and
v2 share it; v1's wire output is unchanged. The guard now surfaces as a
typed migration.ErrMigrationAlreadyRunning (412) so v2 can translate it
through the standard error bridge.
2026-06-11 18:35:55 +00:00
kolaente 809ac118f9 refactor(api/v2): dedup task collection query params via exported embed 2026-06-11 18:31:03 +00:00
kolaente 3bd75acabf feat(api/v2): add task collection (task lists) on /api/v2
Ports v1's task-list surface to /api/v2 as four endpoints. v1 served a
single polymorphic endpoint; v2 makes it monomorphic:

  GET /tasks                                     flat []*Task, all projects
  GET /projects/{project}/tasks                  flat []*Task
  GET /projects/{project}/views/{view}/tasks     flat []*Task (even kanban)
  GET /projects/{project}/views/{view}/buckets/tasks   []*Bucket with tasks

The three task endpoints force flat tasks via TaskCollection so a kanban
view path no longer returns buckets; the dedicated buckets endpoint keeps
the polymorphic kanban branch and is not paginated (bounded by the view's
bucket config). Search is exposed as q; multi-value sort_by/order_by/expand
use ,explode. Hitting the buckets endpoint with a non-kanban view is a 400
rather than a type-mismatch 500.
2026-06-11 18:31:03 +00:00
kolaente 3a84c491ae feat(models): let TaskCollection force a flat task list
v1's TaskCollection.ReadAll is polymorphic: a kanban view returns
[]*Bucket, everything else []*Task. v2 splits the task list into a
flat-tasks endpoint and a separate buckets-with-tasks endpoint, so the
flat endpoint needs ReadAll to return tasks even for a kanban view.
SetForceFlatTasks toggles that; v1 leaves it unset and keeps its shape.
2026-06-11 18:31:03 +00:00
Frederick [Bot] 05b10e34d8 [skip ci] Updated swagger docs 2026-06-11 07:42:32 +00:00
kolaente 28af57bc93 feat(api/v2): add user account/settings on /api/v2
Port the current-user account and settings endpoints from /api/v1 to the
Huma-backed /api/v2, calling the shared orchestration extracted into
models/user/openid:

- GET    /user                            current user + settings + computed
                                          auth_provider/is_local_user/is_admin
- POST   /user/password                   change password (200, creates nothing)
- PUT    /user/settings/email             update email (kicks off confirmation)
- PUT    /user/settings/general           update general settings
- GET    /user/settings/avatar/provider   get avatar provider
- PUT    /user/settings/avatar/provider   set avatar provider
- GET    /user/timezones                  list available time zones

These are current-user-scoped custom handlers (no per-resource Can*): each
pulls the authed user from the request context and operates on it. The avatar
provider get/set live on /user/settings/avatar/provider because v2 already
maps /user/settings/avatar to the binary avatar upload (PUT).
2026-06-11 07:02:31 +00:00
kolaente 46b07a019c refactor(user): extract shared account orchestration into models/user/shared for v1+v2
Pull the business logic out of the v1 current-user account/settings handlers
into reusable functions so both v1 and the upcoming v2 handlers call one
implementation. No behavior change — the v1 handlers keep their HTTP-layer
quirks (input binding, validation, error mapping); only orchestration moves.

Homes are forced by the import graph:
- shared.GetAuthProviderName  (new pkg/routes/api/shared, above openid+user so it
                              can combine both without a cycle; routes-only helper)
- user.ChangeUserEmail        (CheckUserCredentials + UpdateEmail, both in user)
- models.ChangeUserPassword   (needs models.DeleteAllUserSessions; user can't import models)
- models.UpdateUserGeneralSettings / UpdateUserAvatarProvider
                              (need avatar.FlushAllCaches; user can't import avatar)

The general settings get a single shared wire struct, models.UserGeneralSettings
(tagged for both swaggo/govalidator and Huma): it is the update request body and
the nested settings on GET /user for v1 (replacing v1's UserSettings) and v2.
ExtraSettingsLinks is readOnly — populated from the user on read, ignored on
write. A dedicated struct is required because user.User's settings fields are
json:"-" so they don't leak when it is embedded in other responses.
2026-06-11 07:02:31 +00:00
kolaente 154a96674d fix(notifications): strip remote images from notification emails
User-controlled fields rendered into notification emails (task title via the
conversational header, comment and description bodies) were sanitized with a
bluemonday UGCPolicy that permits remote <img> sources. An attacker with write
access to a shared project could therefore inject an external image that acts
as a tracking pixel in a subscriber's inbox, leaking email-open time and IP.

Restrict notification-email images to inline data URIs (used by avatars) by
adding a RewriteSrc hook that blanks any non-data image src. The policy was
duplicated in three places, so extract it into newNotificationSanitizer.

Refs GHSA-2vr2-r3qw-rjvq
2026-06-11 06:53:37 +00:00
kolaente b8894ac1c1 feat(api/v2): add user account-deletion flow on /api/v2 2026-06-10 19:15:05 +00:00
kolaente a610ccbbac feat(api/v2): add user webhooks on /api/v2
Port the per-user webhook endpoints (/user/settings/webhooks) from /api/v1 to
the Huma-backed /api/v2: list, available events, create, update, delete. They
are the project-less sibling of the project webhooks (#2858) and share the
webhooks.enabled gate, checked inside the registrar.

Webhook.ReadAll is extended to serve the user-level list (scoped to the
authenticated user) so the v2 list handler can go through handler.DoReadAll like
the project list; the project branch is unchanged. Credentials are masked on
read via the model's existing maskCredentials, matching #2858.
2026-06-10 19:12:41 +00:00
kolaente 190fab8e6d feat(api/v2): add TOTP 2FA on /api/v2
Ports the current-user TOTP (2FA) endpoints from /api/v1 to the Huma-backed
/api/v2: get status, enroll, enable, and disable. Each is a custom,
current-user-scoped handler that resolves the authenticated user and refuses
non-local (OIDC/LDAP) accounts, preserving v1's local-account-only guard.

The image/jpeg QR-code endpoint is intentionally not ported here; it is a
binary-streaming route deferred to a later wave.
2026-06-10 17:58:16 +00:00
kolaente 4afcfa4441 docs(api/v2): tag TOTP fields for the v2 schema 2026-06-10 17:58:16 +00:00
kolaente a562f69f02 feat(api/v2): add CalDAV tokens on /api/v2 2026-06-10 17:55:52 +00:00
kolaente da3bf0e7cd docs(api/v2): tag CalDAV token fields for the v2 schema 2026-06-10 17:55:52 +00:00
kolaente a1621fec37 feat(api/v2): add task attachments on /api/v2 2026-06-10 10:22:39 +00:00
kolaente dc935f263c docs(api/v2): tag task attachment fields for the v2 schema 2026-06-10 10:22:39 +00:00
kolaente cec74717fc refactor(task-attachment): share upload+download via pkg/web/files for v1+v2 2026-06-10 10:22:39 +00:00
kolaente 5cdc785b49 fix(api/v2): return ErrProjectDoesNotExist for unknown project identifiers 2026-06-10 10:12:09 +00:00
kolaente 0a879e56a8 feat(api/v2): add task CRUD on /api/v2 2026-06-10 10:12:09 +00:00
kolaente 4316554b27 docs(api/v2): tag task fields for the v2 schema 2026-06-10 10:12:09 +00:00
kolaente 328de89c0b feat(api/v2): add bulk label replacement on /api/v2 2026-06-10 11:56:05 +02:00
kolaente 0e0ececa2d docs(api/v2): tag bulk label fields for the v2 schema 2026-06-10 11:56:05 +02:00
kolaente 25a294d7bc feat(api/v2): add task position updates on /api/v2 2026-06-10 11:55:51 +02:00
kolaente a6a073329f docs(api/v2): tag task position fields for the v2 schema 2026-06-10 11:55:51 +02:00
Frederick [Bot] 12f290905a [skip ci] Updated swagger docs 2026-06-09 23:32:10 +00:00
kolaente 1e82c62ff7 feat(api/v2): add reactions on /api/v2 2026-06-09 21:34:22 +00:00
kolaente f5e7e9ddde docs(api/v2): tag reaction fields for the v2 schema 2026-06-09 21:34:22 +00:00
kolaente 2e02fe11ac feat(api/v2): add task relations on /api/v2 2026-06-09 20:42:00 +00:00
kolaente da76d393d9 docs(api/v2): tag task relation fields for the v2 schema 2026-06-09 20:42:00 +00:00
kolaente 5c960fccd5 feat(api/v2): add bulk task updates on /api/v2 2026-06-09 20:13:02 +00:00
kolaente 711545e9f2 docs(api/v2): tag bulk task fields for the v2 schema 2026-06-09 20:13:02 +00:00
kolaente 1aa9493bc3 feat(api/v2): add project duplication on /api/v2 2026-06-09 20:11:43 +00:00
kolaente d5bcbe39b4 docs(api/v2): tag project duplication fields for the v2 schema 2026-06-09 20:11:43 +00:00
kolaente 51e5c86f69 feat(api/v2): add kanban task-bucket moves on /api/v2 2026-06-09 20:01:20 +00:00
kolaente 9eca20fe43 docs(api/v2): tag task bucket fields for the v2 schema 2026-06-09 20:01:20 +00:00
kolaente aa144b9a39 feat(api/v2): add task read-status marking on /api/v2 2026-06-09 19:50:58 +00:00
kolaente bf2a65dcaf feat(api/v2): add bulk assignee replacement on /api/v2 2026-06-09 19:42:16 +00:00
kolaente 732cd115a5 docs(api/v2): tag bulk assignee fields for the v2 schema 2026-06-09 19:42:16 +00:00