feat: admin area with customers, users, settings, audit log, service status (phase 5)
- customer CRUD wizard backend: per-system credential shapes validated via
connector construction, AES-256-GCM at rest, write-only over the API
- ticketing connectors (jira/azure_devops/youtrack/demo) with test-connection
- user management: roles, disable (revokes sessions), force password reset,
delete; self-demotion/disable/delete guards
- admin-editable runtime settings ({_id:app}) incl. atomizer URL override
- audit log written on every privileged mutation + filterable list API
- service status panel: mongo/atomizer/work-performer health + latency with
late-bound breaker, sync and jobs status providers
- tabbed admin UI (customers wizard dialog, users table, settings, status, audit)
- compose: mongo nofile ulimit 64000 (1024 default crashed WiredTiger)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// demoConnector fabricates tickets locally (§11.11) so the entire
|
||||
// import→atomize→publish→claim flow is demonstrable offline. Deterministic:
|
||||
// the same project key always yields the same tickets. A project key ending
|
||||
// in "-ORPHAN" omits the last ticket from ListAssignedKeys to exercise the
|
||||
// orphan flow.
|
||||
type demoConnector struct {
|
||||
projectKey string
|
||||
}
|
||||
|
||||
var demoTickets = []struct {
|
||||
suffix int
|
||||
typ string
|
||||
title string
|
||||
description string
|
||||
}{
|
||||
{1, "epic", "Customer self-service portal",
|
||||
"Build a portal where customers can view invoices, update billing details, and download usage reports. Must integrate with the existing SSO and billing systems."},
|
||||
{2, "story", "User import from CSV",
|
||||
"As an admin I can bulk-import users from a CSV file with column mapping, validation preview, and an idempotent re-run option. Errors are reported per row."},
|
||||
{3, "story", "Email notification preferences",
|
||||
"As a user I can choose which events trigger email notifications (digest, immediate, off) and unsubscribe via a one-click link that does not require login."},
|
||||
{4, "task", "Rate-limit public API endpoints",
|
||||
"Add per-token rate limiting to all public API endpoints with sensible defaults, 429 responses with Retry-After, and metrics for limit hits."},
|
||||
{5, "task", "Fix timezone handling in reports",
|
||||
"Scheduled reports render dates in server time instead of the workspace timezone. Audit all report templates and normalize via the workspace setting."},
|
||||
}
|
||||
|
||||
func (d *demoConnector) Authorize(*http.Request) {}
|
||||
|
||||
func (d *demoConnector) TestConnection(context.Context) error { return nil }
|
||||
|
||||
func (d *demoConnector) key(i int) string {
|
||||
base := strings.TrimSuffix(strings.ToUpper(d.projectKey), "-ORPHAN")
|
||||
if base == "" {
|
||||
base = "DEMO"
|
||||
}
|
||||
return fmt.Sprintf("%s-%d", base, i)
|
||||
}
|
||||
|
||||
func (d *demoConnector) FetchUpdated(_ context.Context, _ string, _ time.Time) ([]Ticket, error) {
|
||||
out := make([]Ticket, 0, len(demoTickets))
|
||||
for _, dt := range demoTickets {
|
||||
out = append(out, Ticket{
|
||||
Key: d.key(dt.suffix),
|
||||
URL: "https://demo.invalid/browse/" + d.key(dt.suffix),
|
||||
Type: dt.typ,
|
||||
Title: dt.title,
|
||||
Description: dt.description,
|
||||
Raw: map[string]any{"demo": true, "suffix": dt.suffix},
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (d *demoConnector) ListAssignedKeys(context.Context, string) ([]string, error) {
|
||||
n := len(demoTickets)
|
||||
if strings.HasSuffix(strings.ToUpper(d.projectKey), "-ORPHAN") {
|
||||
n-- // last ticket "disappears" upstream → orphan flow
|
||||
}
|
||||
keys := make([]string, 0, n)
|
||||
for _, dt := range demoTickets[:n] {
|
||||
keys = append(keys, d.key(dt.suffix))
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
Reference in New Issue
Block a user