package store import ( "context" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" ) type SqliteTenantStore struct { db dbi.DBI guard lib.Guard } var _ iam.TenantStore = (*SqliteTenantStore)(nil) func NewSqliteTenantStore(db dbi.DBI, guard lib.Guard) *SqliteTenantStore { return &SqliteTenantStore{db: db, guard: guard} } func (s *SqliteTenantStore) Create(ctx context.Context, t *iam.Tenant) error { if t.ID.IsEmpty() { return iam.ErrTenantIDEmpty } if err := s.guard.System(ctx, iam.CapTenantsCreate); err != nil { return err } _, err := s.db.Exec(ctx, `INSERT INTO tenants (id, name) VALUES ($1, $2)`, t.ID, t.Name) return err } func (s *SqliteTenantStore) Update(ctx context.Context, t *iam.Tenant) error { if t.ID.IsEmpty() { return iam.ErrTenantIDEmpty } if err := s.guard.Check(ctx, iam.CapTenantsUpdate, t.ID, ""); err != nil { return err } res, err := s.db.Exec(ctx, `UPDATE tenants SET name = $1 WHERE id = $2`, t.Name, t.ID) if err != nil { return err } n, err := res.RowsAffected() if err != nil { return err } if n == 0 { return core.ErrNotFound } return nil } func (s *SqliteTenantStore) Get(ctx context.Context, id core.ID, out *iam.Tenant) error { if err := s.guard.Check(ctx, iam.CapTenantsRead, id, ""); err != nil { return err } return dbi.Get(ctx, s.db, out, `SELECT id, name FROM tenants WHERE id = $1`, id) } func (s *SqliteTenantStore) Delete(ctx context.Context, id core.ID) error { if err := s.guard.Check(ctx, iam.CapTenantsDelete, id, ""); err != nil { return err } _, err := s.db.Exec(ctx, `DELETE FROM tenants WHERE id = $1`, id) return err } func (s *SqliteTenantStore) List(ctx context.Context, page core.PageReq, out *core.Page[iam.Tenant]) error { // TODO this seems to ignore non-nil errors that are not access denied and let's that fall through // which feels slightly sloppy. The interface also makes distinguishing expected vs non-expected // errors slightly clunky. if err := s.guard.System(ctx, iam.CapTenantsRead); err == nil { return dbi.Paginate(ctx, s.db, page, out, func(t iam.Tenant) string { return t.ID.String() }, `SELECT id, name FROM tenants WHERE id > $cursor ORDER BY id LIMIT $limit`, ) } // The caller is querying for their own tenants. principal := iam.GetPrincipal(ctx) return dbi.Paginate(ctx, s.db, page, out, func(m iam.Tenant) string { return m.ID.String() }, `SELECT t.id, t.name FROM tenants t JOIN tenant_members tm ON t.id = tm.tenant WHERE tm.user_id = $1 and t.id > $cursor ORDER BY t.id LIMIT $limit`, principal.Subject) }