Commit Graph

104 Commits

Author SHA1 Message Date
clawbot
df6aad9b21 refactor: POST env vars as JSON array instead of KEY=value string
All checks were successful
Check / check (pull_request) Successful in 4s
Replace the string-serialized KEY=value format with a proper JSON array
of {key, value} objects for the env var save endpoint.

Frontend changes:
- envVarEditor.submitAll() now uses fetch() with Content-Type:
  application/json and X-CSRF-Token header instead of form submission
- Sends JSON array: [{"key":"FOO","value":"bar"}, ...]
- Hidden bulk form replaced with hidden div holding CSRF token
- envVarEditor now receives appId parameter for the fetch URL

Backend changes:
- HandleEnvVarSave reads JSON body via json.NewDecoder instead of
  parsing form values with parseEnvPairs
- Returns JSON {"ok": true} instead of HTTP redirect
- Removed parseEnvPairs function and envPair struct entirely
- Added envPairJSON struct with json tags for deserialization

Tests updated to POST JSON arrays instead of form-encoded strings.

Closes #163
2026-03-10 11:37:55 -07:00
clawbot
b3cda1515f feat: monolithic env var editing (bulk save, no per-var CRUD)
All checks were successful
Check / check (pull_request) Successful in 4s
Replace individual env var add/edit/delete with a single bulk save
endpoint. The UI now shows a textarea with KEY=VALUE lines. On save,
all existing env vars are deleted and the full submitted set is
inserted.

- Replace HandleEnvVarAdd, HandleEnvVarEdit, HandleEnvVarDelete with
  HandleEnvVarSave
- Collapse 3 routes into single POST /apps/{id}/env
- Template uses textarea instead of per-row edit/delete forms
- No individual env var IDs exposed in the UI
- Extract parseEnvPairs helper to keep cyclomatic complexity low
- Use strings.SplitSeq per modernize linter
- Update tests for new bulk save behavior

closes #156
closes #163
2026-03-10 11:05:19 -07:00
4aaeffdffc Merge branch 'main' into fix/issue-156-env-vars-404
All checks were successful
Check / check (pull_request) Successful in 1m42s
2026-03-10 18:54:32 +01:00
e1dc865226 feat: add webhook event history UI page (#164)
All checks were successful
Check / check (push) Successful in 4s
## Summary

Adds a per-app webhook event history page at `/apps/{id}/webhooks` showing received webhook events with match/no-match status.

## Changes

- **New template** `webhook_events.html` — displays webhook events in a table with time, event type, branch, commit SHA (linked when URL available), and match status badges
- **New handler** `HandleAppWebhookEvents()` in `webhook_events.go` — fetches app and its webhook events (limit 100)
- **New route** `GET /apps/{id}/webhooks` — registered in protected routes group
- **Template registration** — added `webhook_events.html` to the template cache in `templates.go`
- **Model enhancement** — added `ShortCommit()` method to `WebhookEvent` for truncated SHA display
- **App detail link** — added "Event History" link in the Webhook URL card on the app detail page

## UI

Follows the existing UI patterns (Tailwind CSS classes, Alpine.js `relativeTime`, badge styles, empty state, back-navigation). The page mirrors the deployments history page layout.

closes [#85](#85)

Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Reviewed-on: #164
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-10 18:53:58 +01:00
2d04ff85aa Merge branch 'main' into fix/issue-156-env-vars-404
All checks were successful
Check / check (pull_request) Successful in 1m40s
2026-03-10 01:08:05 +01:00
ab63670043 fix: pass notification settings from create form to service (#160)
All checks were successful
Check / check (push) Successful in 3m49s
## Summary

`HandleAppCreate` was not reading `docker_network`, `ntfy_topic`, or `slack_webhook` form values from the create app form. These fields were silently dropped during app creation, even though:
- `app_new.html` had the form fields
- `CreateAppInput` had the corresponding struct fields
- `CreateApp` already handled them correctly

The edit/update flow was unaffected — the bug was exclusively in the create path.

## Changes

- Read `docker_network`, `ntfy_topic`, `slack_webhook` form values in `HandleAppCreate`
- Pass them to `CreateAppInput`
- Include them in template re-render data (preserves values on validation errors)

closes #157

<!-- session: agent:sdlc-manager:subagent:1fb3582d-1eff-4309-b166-df5046a1b885 -->

Co-authored-by: clawbot <clawbot@noreply.git.eeqj.de>
Reviewed-on: #160
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-10 01:01:32 +01:00
clawbot
30f81078bd fix: use /env routes for env var CRUD, fixing 404 on env var forms
All checks were successful
Check / check (pull_request) Successful in 3m7s
Change route patterns in routes.go from /env-vars to /env and update
edit/delete form actions in app_detail.html to match. The add form
already used /env and was correct.

Update test route setup to match the new /env paths.

Closes #156
2026-03-06 03:50:17 -08:00
12446f9f79 fix: change module path to sneak.berlin/go/upaas (closes #143) (#149)
All checks were successful
Check / check (push) Successful in 1m16s
Changes the Go module path from `git.eeqj.de/sneak/upaas` to `sneak.berlin/go/upaas`.

All import paths in Go files updated accordingly. `go mod tidy` and `make check` pass cleanly.

fixes #143

Co-authored-by: user <user@Mac.lan guest wan>
Co-authored-by: Jeffrey Paul <sneak@noreply.example.org>
Reviewed-on: #149
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
2026-03-01 23:22:18 +01:00
user
c22a2877d5 fix: pass CSRFField to dashboard template (closes #146)
All checks were successful
Check / check (pull_request) Successful in 2m30s
2026-02-26 02:56:27 -08:00
user
43cde0eefd test: add failing test for dashboard CSRFField (refs #146) 2026-02-26 02:56:00 -08:00
user
f36732eaf5 refactor: remove internal/domain package, move types to correct packages
- ImageID + ContainerID → internal/docker/types.go
- UnparsedURL → internal/service/webhook/types.go
- Delete internal/domain/ entirely
- Update all imports throughout the codebase
2026-02-26 02:01:12 -08:00
user
3a1b1e3cd4 refactor: add String() methods to domain types, replace string() casts 2026-02-26 02:01:12 -08:00
594537e6f5 rework: address review feedback on PR #126
Changes per sneak's review:
- Delete docker-compose.yml, add example stanza to README
- Define custom domain types: ImageID, ContainerID, UnparsedURL
- Use custom types in all function signatures throughout codebase
- Restore imageID parameter (as domain.ImageID) in deploy pipeline
- buildContainerOptions now takes ImageID directly instead of
  constructing image tag from deploymentID
- Fix pre-existing JS formatting (prettier)

make check passes with zero failures.
2026-02-26 02:01:12 -08:00
a6c76232bf fix: assign commit error to err so deferred rollback triggers (closes #125)
When Commit() failed, the error was stored in commitErr instead of err,
so the deferred rollback (which checks err) was skipped.
2026-02-26 02:00:49 -08:00
46574f8cf1 fix: rename GetBuildDir param from appID to appName (closes #123)
The parameter is always called with app.Name, not an ID. Rename to match
actual usage and prevent confusion.
2026-02-26 02:00:49 -08:00
074903619d fix: add 1MB size limit on deployment logs with truncation (closes #122)
Cap AppendLog at 1MB, truncating oldest lines when exceeded. Prevents
unbounded SQLite database growth from long-running builds.
2026-02-26 02:00:49 -08:00
6cf6e89db4 fix: use renderTemplate in all error paths of HandleAppCreate/HandleAppUpdate (closes #121)
Replace direct tmpl.ExecuteTemplate calls with h.renderTemplate to ensure
buffered rendering and prevent partial HTML responses on template errors.
2026-02-26 02:00:49 -08:00
user
0e8efe1043 fix: use imageID in createAndStartContainer (closes #124)
All checks were successful
Check / check (pull_request) Successful in 11m24s
Wire the imageID parameter (returned from docker build) through
createAndStartContainer and buildContainerOptions instead of
reconstructing a mutable tag via fmt.Sprintf.

This ensures containers reference the immutable image digest,
avoiding tag-reuse races when deploys overlap.

Changes:
- Rename _ string to imageID string in createAndStartContainer
- Change buildContainerOptions to accept imageID string instead of deploymentID int64
- Use imageID directly as the Image field in container options
- Update rollback path to pass previousImageID directly
- Add test verifying imageID flows through to container options
- Add database.NewTestDatabase and logger.NewForTest test helpers
2026-02-21 02:24:51 -08:00
user
ab7c43b887 fix: disable API v1 write methods (closes #112)
All checks were successful
Check / check (pull_request) Successful in 11m21s
Remove POST /apps, DELETE /apps/{id}, and POST /apps/{id}/deploy from
the API v1 route group. These endpoints used cookie-based session auth
without CSRF protection, creating a CSRF vulnerability.

Read-only endpoints (GET /apps, GET /apps/{id}, GET /apps/{id}/deployments),
login, and whoami are retained.

Removed handlers: HandleAPICreateApp, HandleAPIDeleteApp,
HandleAPITriggerDeploy, along with apiCreateRequest struct and
validateCreateRequest function.

Updated tests to use service layer directly for app creation in
remaining read-only endpoint tests.
2026-02-20 05:33:07 -08:00
clawbot
327d7fb982 fix: resolve lint issues in handlers and middleware
All checks were successful
Check / check (pull_request) Successful in 11m26s
2026-02-20 03:35:44 -08:00
clawbot
6cfd5023f9 fix: SetupRequired middleware exempts health, static, and API routes (closes #108) 2026-02-20 03:33:34 -08:00
clawbot
efd3500dac fix: HandleVolumeAdd validates host and container paths (closes #107) 2026-02-20 03:33:19 -08:00
clawbot
ec87915234 fix: API delete endpoint cleans up Docker container before DB deletion (closes #106) 2026-02-20 03:33:04 -08:00
clawbot
cd0354e86c fix: API deploy handler uses detached context to prevent cancellation (closes #105) 2026-02-20 03:32:42 -08:00
clawbot
7d1849c8df fix: HandleEnvVarDelete uses correct varID route param (closes #104) 2026-02-20 03:32:20 -08:00
aab2375cfa Merge branch 'main' into chore/code-cleanup 2026-02-20 11:59:06 +01:00
user
0bb59bf9c2 feat: sanitize container log output beyond Content-Type
Add SanitizeLogs() that strips ANSI escape sequences and non-printable
control characters (preserving newlines, carriage returns, and tabs)
from all container and deployment log output paths:

- HandleAppLogs (text/plain response)
- HandleDeploymentLogsAPI (JSON response)
- HandleContainerLogsAPI (JSON response)

Container log output is attacker-controlled data. Content-Type alone
is insufficient — the data itself must be sanitized before serving.

Includes comprehensive test coverage for the sanitization function.
2026-02-20 02:54:16 -08:00
clawbot
a2087f4898 fix: restrict SCP-like URLs to git user only and reject path traversal
- Changed SCP regex to only accept 'git' as the username
- Added path traversal check: reject URLs containing '..'
- Added test cases for non-git users and path traversal
2026-02-20 02:51:38 -08:00
clawbot
a2fb42520d fix: validate repo URL format on app creation (closes #88) 2026-02-20 02:51:38 -08:00
clawbot
0fcf12d2cc fix: resolve all lint issues on main branch
- funcorder: reorder RemoveImage before unexported methods in docker/client.go
- gosec G117: add json:"-" tags to SessionSecret and PrivateKey fields
- gosec G117: replace login struct with map to avoid secret pattern match
- gosec G705: add #nosec for text/plain XSS false positive
- gosec G703: add #nosec for internal path traversal false positive
- gosec G704: validate URLs and add #nosec for config-sourced SSRF false positives
- gosec G306: use 0o600 permissions in test file
- revive: rename unused parameters to _
- wsl_v5: add missing blank line before assignment
2026-02-20 02:39:18 -08:00
clawbot
728b29ef16 Revert "Merge pull request 'feat: add Gitea Actions CI for make check (closes #96)' (#98) from feat/ci-make-check into main"
This reverts commit f61d4d0f91, reversing
changes made to 06e8e66443.
2026-02-19 20:36:22 -08:00
clawbot
8ec04fdadb feat: add Gitea Actions CI for make check (closes #96)
Some checks failed
check / check (pull_request) Failing after 16s
- Add .gitea/workflows/check.yml running make check on PRs and pushes to main
- Fix .golangci.yml for golangci-lint v2 config format (was using v1 keys)
- Migrate linters-settings to linters.settings, remove deprecated exclude-use-default
- Exclude gosec false positives (G117, G703, G704, G705) with documented rationale
- Increase lll line-length from 88 to 120 (88 was too restrictive for idiomatic Go)
- Increase dupl threshold from 100 to 150 (similar CRUD handlers are intentional)
- Fix funcorder: move RemoveImage before unexported methods in docker/client.go
- Fix wsl_v5: add required blank line in deploy.go
- Fix revive unused-parameter in export_test.go
- Fix gosec G306: tighten test file permissions to 0600
- Add html.EscapeString for log output, filepath.Clean for log path
- Remove stale //nolint:funlen directives no longer needed with v2 config
2026-02-19 20:29:21 -08:00
clawbot
95a690e805 fix: use strings.HasPrefix instead of manual slice comparison
- Replace entry.Name()[:len(prefix)] == prefix with strings.HasPrefix
- Applied consistently in both deploy.go and export_test.go
2026-02-19 20:17:27 -08:00
clawbot
802518b917 fix: clean up orphan resources on deploy cancellation (closes #89) 2026-02-19 20:15:22 -08:00
clawbot
02847eea92 fix: restrict CORS to configured origins (closes #40)
- Add CORSOrigins config field (UPAAS_CORS_ORIGINS env var)
- Default to same-origin only (no CORS headers when unconfigured)
- When configured, allow specified origins with AllowCredentials: true
- Add tests for CORS middleware behavior
2026-02-19 13:45:18 -08:00
clawbot
506c795f16 test: add CORS middleware tests (failing - TDD) 2026-02-19 13:43:33 -08:00
11314629b6 Merge branch 'main' into feature/json-api 2026-02-16 09:51:36 +01:00
user
8d68a31366 fix: remove undeployed api_tokens migrations (006 + 007) 2026-02-16 00:34:02 -08:00
f743837d49 Merge branch 'main' into feature/json-api 2026-02-16 09:33:09 +01:00
user
9ac1d25788 refactor: switch API from token auth to cookie-based session auth
- Remove API token system entirely (model, migration, middleware)
- Add migration 007 to drop api_tokens table
- Add POST /api/v1/login endpoint for JSON credential auth
- API routes now use session cookies (same as web UI)
- Remove /api/v1/tokens endpoint
- HandleAPIWhoAmI uses session auth instead of token context
- APISessionAuth middleware returns JSON 401 instead of redirect
- Update all API tests to use cookie-based authentication

Addresses review comment on PR #74.
2026-02-16 00:31:10 -08:00
0c8dcc2eb1 Merge branch 'main' into feature/edit-config-entities 2026-02-16 09:28:30 +01:00
e9d284698a feat: edit existing env vars, labels, and volume mounts
Add inline edit functionality for environment variables, labels, and
volume mounts on the app detail page. Each entity row now has an Edit
button that reveals an inline form using Alpine.js.

- POST /apps/{id}/env-vars/{varID}/edit
- POST /apps/{id}/labels/{labelID}/edit
- POST /apps/{id}/volumes/{volumeID}/edit
- Path validation for volume host and container paths
- Warning banner about container restart after env var changes
- Tests for ValidateVolumePath

fixes #67
2026-02-16 00:26:07 -08:00
user
0536f57ec2 feat: add JSON API with token auth (closes #69)
- Add API token model with SHA-256 hashed tokens
- Add migration 006_add_api_tokens.sql
- Add Bearer token auth middleware
- Add API endpoints under /api/v1/:
  - GET /whoami
  - POST /tokens (create new API token)
  - GET /apps (list all apps)
  - POST /apps (create app)
  - GET /apps/{id} (get app)
  - DELETE /apps/{id} (delete app)
  - POST /apps/{id}/deploy (trigger deployment)
  - GET /apps/{id}/deployments (list deployments)
- Add comprehensive tests for all API endpoints
- All tests pass, zero lint issues
2026-02-16 00:24:45 -08:00
user
2be6a748b7 feat: deployment rollback to previous image
- Add previous_image_id column to apps table (migration 006)
- Save current image as previous before deploying new one
- POST /apps/{id}/rollback endpoint with handler
- Rollback stops current container, starts previous image
- Creates deployment record for rollback operations
- Rollback button in app detail UI (only when previous image exists)
- Add btn-warning CSS class for rollback button styling

fixes #71
2026-02-16 00:23:11 -08:00
user
c5f957477f feat: add user-facing deployment cancel endpoint
Add POST /apps/{id}/deployments/cancel endpoint that allows users to
cancel in-progress deployments via the web UI.

Changes:
- Add CancelDeploy() and HasActiveDeploy() public methods to deploy service
- Add HandleCancelDeploy handler
- Wire route in routes.go
- Add cancel button to app detail template (shown during active deployments)
- Add handler tests for cancel endpoint

fixes #66
2026-02-16 00:15:24 -08:00
ebcae55302 Merge pull request 'fix: cancel in-progress deploy when webhook triggers new deploy (closes #38)' (#52) from clawbot/upaas:fix/deploy-race-condition-38 into main
Reviewed-on: #52
2026-02-16 09:06:40 +01:00
user
a80b7ac0a6 refactor: export SanitizeTail and DefaultLogTail directly instead of wrapping
- Rename sanitizeTail → SanitizeTail (exported)
- Rename defaultLogTail → DefaultLogTail (exported)
- Delete export_test.go (no longer needed)
- Update test to reference handlers.SanitizeTail/DefaultLogTail directly
2026-02-15 22:14:12 -08:00
clawbot
69a5a8c298 fix: resolve all golangci-lint issues (fixes #32) 2026-02-15 22:13:12 -08:00
3f499163a7 fix: cancel in-progress deploy when webhook triggers new deploy (closes #38)
When a webhook-triggered deploy starts for an app that already has a deploy
in progress, the existing deploy is now cancelled via context cancellation
before the new deploy begins. This prevents silently lost webhook deploys.

Changes:
- Add per-app active deploy tracking with cancel func and done channel
- Deploy() accepts cancelExisting param: true for webhook, false for manual
- Cancelled deployments are marked with new 'cancelled' status
- Add ErrDeployCancelled sentinel error
- Add DeploymentStatusCancelled model constant
- Add comprehensive tests for cancellation mechanics
2026-02-15 22:12:03 -08:00
07ac71974c Merge pull request 'fix: set DestroySession MaxAge to -1 instead of -1*time.Second (closes #39)' (#50) from clawbot/upaas:fix/destroy-session-maxage into main
Reviewed-on: #50
2026-02-16 07:09:25 +01:00