Files
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

69 lines
3.4 KiB
Go

package callmemory
import (
"context"
"data-terminal-cloud/internal/identity"
"encoding/json"
"github.com/jackc/pgx/v5"
)
type AccessInput struct {
Elder string `json:"elder_id"`
User string `json:"user_id,omitempty"`
Read bool `json:"can_read"`
Write bool `json:"can_write"`
Sensitive bool `json:"sensitive_read"`
Version int64 `json:"expected_version"`
}
func (s *Service) accessRequest(ctx context.Context, tx pgx.Tx, p identity.Principal, op string, raw []byte) (any, error) {
var in AccessInput
if e := StrictJSON(raw, &in); e != nil {
return nil, e
}
a, e := ResolveAccess(ctx, tx, p, in.Elder, false)
if e != nil {
return nil, e
}
if !a.Manage {
return nil, fault(403, "MEMORY_ACCESS_MANAGE_DENIED")
}
if op == "access/grant" {
if !validUUID(in.User) || in.Version < 0 || (!in.Read && (in.Write || in.Sensitive)) {
return nil, fault(400, "INVALID_MEMORY_GRANT")
}
var user, role string
e = tx.QueryRow(ctx, `SELECT ua.id::text,hm.role FROM user_account ua JOIN household_member hm ON hm.user_id=ua.id JOIN subject_profile sp ON sp.household_id=hm.household_id WHERE sp.id=$1::uuid AND ua.public_id=$2::uuid AND hm.status='active' AND ua.status='active' FOR UPDATE OF hm`, a.Subject, in.User).Scan(&user, &role)
if e != nil {
return nil, fault(403, "MEMORY_ACCESS_DENIED")
}
if role == "owner" {
return nil, fault(409, "OWNER_MEMORY_ACCESS_REQUIRED")
}
_, e = tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtextextended($1,15))`, a.Subject+":"+user)
if e != nil {
return nil, e
}
var current int64
e = tx.QueryRow(ctx, `SELECT COALESCE((SELECT version FROM memory_access_grant WHERE subject_id=$1::uuid AND user_id=$2::uuid),0)`, a.Subject, user).Scan(&current)
if e != nil {
return nil, e
}
if current != in.Version {
return nil, fault(409, "GRANT_VERSION_CONFLICT")
}
_, e = tx.Exec(ctx, `INSERT INTO memory_access_grant(subject_id,user_id,can_read,can_write,sensitive_read,version) VALUES($1::uuid,$2::uuid,$3,$4,$5,1) ON CONFLICT(subject_id,user_id) DO UPDATE SET can_read=excluded.can_read,can_write=excluded.can_write,sensitive_read=excluded.sensitive_read,version=memory_access_grant.version+1`, a.Subject, user, in.Read, in.Write, in.Sensitive)
if e != nil {
return nil, e
}
// Resolving a proposal does not preserve authority after this grant is revoked.
_, e = tx.Exec(ctx, `UPDATE memory_mutation SET confirmation_hash=NULL,proposal_digest=NULL,confirmation_expires_at=NULL,revision=revision+1 WHERE subject_id=$1::uuid AND actor_id=$2::uuid AND state='NEEDS_CONFIRMATION'`, a.Subject, user)
if e != nil {
return nil, e
}
}
var data []byte
e = tx.QueryRow(ctx, `SELECT COALESCE(jsonb_agg(jsonb_build_object('user_id',ua.public_id,'display_name',ua.display_name,'owner',hm.role='owner','can_read',hm.role='owner' OR COALESCE(g.can_read,false),'can_write',hm.role='owner' OR COALESCE(g.can_write,false),'sensitive_read',hm.role='owner' OR COALESCE(g.sensitive_read,false),'version',COALESCE(g.version,0)) ORDER BY ua.display_name),'[]'::jsonb) FROM subject_profile sp JOIN household_member hm ON hm.household_id=sp.household_id JOIN user_account ua ON ua.id=hm.user_id LEFT JOIN memory_access_grant g ON g.subject_id=sp.id AND g.user_id=ua.id WHERE sp.id=$1::uuid AND hm.status='active' AND ua.status='active'`, a.Subject).Scan(&data)
return map[string]any{"items": json.RawMessage(data)}, e
}