package sshkey import ( "context" "database/sql" "errors" ) type SQLRepository struct { db *sql.DB } func NewSQLRepository(db *sql.DB) SQLRepository { return SQLRepository{db: db} } func (r SQLRepository) List(ctx context.Context, profileID string, tenantID string) ([]SSHKey, bool, error) { rows, err := r.db.QueryContext(ctx, ` select sk.id::text, sk.tenant_id::text, sk.name, sk.public_key, sk.created_at from public.tenants t join public.memberships m on m.tenant_id = t.id and m.profile_id = $1 join public.ssh_keys sk on sk.tenant_id = t.id where t.id = $2 order by sk.created_at desc, sk.name asc `, profileID, tenantID) if err != nil { return nil, false, err } defer rows.Close() var keys []SSHKey for rows.Next() { var k SSHKey if err := rows.Scan(&k.ID, &k.TenantID, &k.Name, &k.PublicKey, &k.CreatedAt); err != nil { return nil, false, err } keys = append(keys, k) } if err := rows.Err(); err != nil { return nil, false, err } if len(keys) == 0 { exists, err := r.tenantExists(ctx, profileID, tenantID) if err != nil { return nil, false, err } return keys, exists, nil } return keys, true, nil } func (r SQLRepository) Get(ctx context.Context, profileID string, keyID string) (SSHKey, bool, error) { var k SSHKey err := r.db.QueryRowContext(ctx, ` select sk.id::text, sk.tenant_id::text, sk.name, sk.public_key, sk.created_at from public.ssh_keys sk join public.memberships m on m.tenant_id = sk.tenant_id and m.profile_id = $1 where sk.id = $2 `, profileID, keyID).Scan(&k.ID, &k.TenantID, &k.Name, &k.PublicKey, &k.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return SSHKey{}, false, nil } if err != nil { return SSHKey{}, false, err } return k, true, nil } func (r SQLRepository) Create(ctx context.Context, profileID string, tenantID string, name string, publicKey string) (SSHKey, bool, error) { exists, err := r.tenantMemberExists(ctx, profileID, tenantID) if err != nil { return SSHKey{}, false, err } if !exists { return SSHKey{}, false, nil } var k SSHKey err = r.db.QueryRowContext(ctx, ` insert into public.ssh_keys (tenant_id, name, public_key) values ($1, $2, $3) returning id::text, tenant_id::text, name, public_key, created_at `, tenantID, name, publicKey).Scan(&k.ID, &k.TenantID, &k.Name, &k.PublicKey, &k.CreatedAt) if err != nil { return SSHKey{}, false, err } return k, true, nil } func (r SQLRepository) Delete(ctx context.Context, profileID string, keyID string) (bool, bool, error) { var tenantID string err := r.db.QueryRowContext(ctx, ` select sk.tenant_id::text from public.ssh_keys sk where sk.id = $1 `, keyID).Scan(&tenantID) if errors.Is(err, sql.ErrNoRows) { return false, false, nil } if err != nil { return false, false, err } member, err := r.tenantMemberExists(ctx, profileID, tenantID) if err != nil { return false, false, err } if !member { return false, true, nil } res, err := r.db.ExecContext(ctx, ` delete from public.ssh_keys where id = $1 `, keyID) if err != nil { return false, false, err } rowsAffected, err := res.RowsAffected() if err != nil { return false, false, err } return rowsAffected > 0, true, nil } func (r SQLRepository) tenantExists(ctx context.Context, profileID string, tenantID string) (bool, error) { var exists bool err := r.db.QueryRowContext(ctx, ` select exists( select 1 from public.tenants t join public.memberships m on m.tenant_id = t.id and m.profile_id = $1 where t.id = $2 ) `, profileID, tenantID).Scan(&exists) return exists, err } func (r SQLRepository) tenantMemberExists(ctx context.Context, profileID string, tenantID string) (bool, error) { var exists bool err := r.db.QueryRowContext(ctx, ` select exists( select 1 from public.memberships where profile_id = $1 and tenant_id = $2 ) `, profileID, tenantID).Scan(&exists) return exists, err }