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,56 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type TicketingType string
|
||||
|
||||
const (
|
||||
TicketingJira TicketingType = "jira"
|
||||
TicketingAzure TicketingType = "azure_devops"
|
||||
TicketingYouTrack TicketingType = "youtrack"
|
||||
// TicketingDemo fabricates tickets locally so the whole flow works
|
||||
// offline (§11.11).
|
||||
TicketingDemo TicketingType = "demo"
|
||||
)
|
||||
|
||||
func (t TicketingType) Valid() bool {
|
||||
switch t {
|
||||
case TicketingJira, TicketingAzure, TicketingYouTrack, TicketingDemo:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Ticketing struct {
|
||||
Type TicketingType `bson:"type" json:"type"`
|
||||
BaseURL string `bson:"baseUrl" json:"baseUrl"`
|
||||
CredentialsEnc string `bson:"credentialsEnc" json:"-"`
|
||||
ProjectKey string `bson:"projectKey" json:"projectKey"`
|
||||
PollIntervalSec int `bson:"pollIntervalSec" json:"pollIntervalSec"`
|
||||
LastSyncAt time.Time `bson:"lastSyncAt" json:"lastSyncAt"`
|
||||
LastSyncStatus string `bson:"lastSyncStatus" json:"lastSyncStatus"` // "", ok, error
|
||||
LastSyncError string `bson:"lastSyncError" json:"lastSyncError"`
|
||||
}
|
||||
|
||||
// Customer == "project" in the UI (§4.2).
|
||||
type Customer struct {
|
||||
ID string `bson:"_id" json:"id"`
|
||||
Name string `bson:"name" json:"name"`
|
||||
Ticketing Ticketing `bson:"ticketing" json:"ticketing"`
|
||||
ConsultantIDs []string `bson:"consultantIds" json:"consultantIds"`
|
||||
DefaultBudget float64 `bson:"defaultBudget" json:"defaultBudget"`
|
||||
Archived bool `bson:"archived" json:"archived"`
|
||||
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
||||
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
Version int64 `bson:"version" json:"version"`
|
||||
}
|
||||
|
||||
// HasConsultant reports membership in the customer's consultant set.
|
||||
func (c *Customer) HasConsultant(userID string) bool {
|
||||
for _, id := range c.ConsultantIDs {
|
||||
if id == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
// AppSettings is the single admin-editable runtime settings document
|
||||
// ({_id:"app"}, §4.8). Env vars are the defaults; these override where the
|
||||
// spec marks them overridable.
|
||||
type AppSettings struct {
|
||||
ID string `bson:"_id" json:"-"`
|
||||
BrandingName string `bson:"brandingName" json:"brandingName"`
|
||||
AtomizerBaseURLOverride string `bson:"atomizerBaseUrlOverride" json:"atomizerBaseUrlOverride"`
|
||||
HideCompetingClaims bool `bson:"hideCompetingClaims" json:"hideCompetingClaims"`
|
||||
DefaultPollIntervalSec int `bson:"defaultPollIntervalSec" json:"defaultPollIntervalSec"`
|
||||
DefaultCustomerBudget float64 `bson:"defaultCustomerBudget" json:"defaultCustomerBudget"`
|
||||
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
Version int64 `bson:"version" json:"version"`
|
||||
}
|
||||
Reference in New Issue
Block a user