package api import ( "database/sql" "net/http" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" ) type TenantsImpl struct { DB *sql.DB Guard lib.Guard Tenants dbi.Factory[iam.TenantStore] Members dbi.Factory[iam.TenantMemberStore] Grants dbi.Factory[iam.GrantStore] } func (s *TenantsImpl) Create(w http.ResponseWriter, r *http.Request) { var req Tenants_CreateReq if read(w, r, &req) { return } if checkSystem(w, r, s.Guard, iam.CapTenantsCreate) { return } ctx := r.Context() userID, err := core.ParseID(iam.GetPrincipal(ctx).Subject) if err != nil { writeErr(ctx, w, iam.ErrForbidden) return } err = dbi.ReadWrite(ctx, s.DB, func(tx dbi.DBI) error { if err := s.Tenants(tx).Create(ctx, &req.Tenant); err != nil { return err } // Establish the caller as owner of the newly-created tenant so they // can subsequently update it (the Create-implies-Update invariant). // No tenant-scoped grants exist yet on this fresh ID, so escalate. sysCtx := lib.PutSystemAccess(ctx, iam.CapTenantMembersCreate, iam.CapGrantsAdd, ) if err := s.Members(tx).Create(sysCtx, iam.TenantMember{ Tenant: req.Tenant.ID, UserID: userID, Owner: true, }); err != nil { return err } return s.Grants(tx).Add(sysCtx, iam.Grant{ Tenant: req.Tenant.ID, Type: iam.GrantTypeUser, Principal: userID.String(), Role: "owner", }) }) write(ctx, w, err, Tenants_CreateRes{Tenant: req.Tenant}) } func (s *TenantsImpl) Update(w http.ResponseWriter, r *http.Request) { var req Tenants_UpdateReq if read(w, r, &req) { return } if check(w, r, s.Guard, iam.CapTenantsUpdate, req.Tenant.ID, "") { return } ctx := r.Context() err := dbi.ReadWrite(ctx, s.DB, func(tx dbi.DBI) error { return s.Tenants(tx).Update(ctx, &req.Tenant) }) write(ctx, w, err, Tenants_UpdateRes{Tenant: req.Tenant}) } func (s *TenantsImpl) Get(w http.ResponseWriter, r *http.Request) { // Read and validate the request body var req Tenants_GetReq if read(w, r, &req) { return } // Check access if check(w, r, s.Guard, iam.CapTenantsRead, req.ID, "") { return } // Get tenant from database ctx := r.Context() var res Tenants_GetRes err := dbi.ReadOnly(ctx, s.DB, func(tx dbi.DBI) error { return s.Tenants(tx).Get(ctx, req.ID, &res.Tenant) }) write(ctx, w, err, res) } func (s *TenantsImpl) Delete(w http.ResponseWriter, r *http.Request) { // Read and validate the request body var req Tenants_DeleteReq if read(w, r, &req) { return } // Check access if check(w, r, s.Guard, iam.CapTenantsDelete, req.ID, "") { return } // Delete the record from the database ctx := r.Context() err := dbi.ReadWrite(ctx, s.DB, func(tx dbi.DBI) error { return s.Tenants(tx).Delete(ctx, req.ID) }) write(ctx, w, err, nil) } func (s *TenantsImpl) List(w http.ResponseWriter, r *http.Request) { // Read and validate the request body var req Tenants_ListReq if read(w, r, &req) { return } // Check access if checkSystem(w, r, s.Guard, iam.CapTenantsRead) { return } // Load the data from the database ctx := r.Context() var res Tenants_ListRes err := dbi.ReadOnly(ctx, s.DB, func(tx dbi.DBI) error { return s.Tenants(tx).List(ctx, req.Page, &res.Page) }) write(ctx, w, err, res) }