init
This commit is contained in:
commit
b15b95781c
108 changed files with 14802 additions and 0 deletions
761
internal/db/apikeys.go
Normal file
761
internal/db/apikeys.go
Normal file
|
|
@ -0,0 +1,761 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreateAPIKeyParams struct {
|
||||
Name string
|
||||
KeyPrefix string
|
||||
KeyHash string
|
||||
Description string
|
||||
ScopeMode ScopeMode
|
||||
CanDownload bool
|
||||
CanUpload bool
|
||||
CanDelete bool
|
||||
CanManageProjects bool
|
||||
IsActive bool
|
||||
ExpiresAt *time.Time
|
||||
CreatedByUserID *int64
|
||||
}
|
||||
|
||||
type UpdateAPIKeyParams struct {
|
||||
Name string
|
||||
Description string
|
||||
ScopeMode ScopeMode
|
||||
CanDownload bool
|
||||
CanUpload bool
|
||||
CanDelete bool
|
||||
CanManageProjects bool
|
||||
ExpiresAt *time.Time
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) List(ctx context.Context) ([]APIKeyListItem, error) {
|
||||
rows, err := r.q.QueryContext(
|
||||
ctx,
|
||||
`SELECT
|
||||
ak.id,
|
||||
ak.name,
|
||||
ak.key_prefix,
|
||||
ak.key_hash,
|
||||
ak.description,
|
||||
ak.scope_mode,
|
||||
ak.can_download,
|
||||
ak.can_upload,
|
||||
ak.can_delete,
|
||||
ak.can_manage_projects,
|
||||
ak.is_active,
|
||||
ak.expires_at,
|
||||
ak.created_at,
|
||||
ak.updated_at,
|
||||
ak.last_used_at,
|
||||
ak.created_by_user_id,
|
||||
COUNT(DISTINCT ap.project_id) AS project_rule_count,
|
||||
COUNT(DISTINCT at.tag_id) AS tag_rule_count
|
||||
FROM api_keys AS ak
|
||||
LEFT JOIN api_key_project_access AS ap ON ap.api_key_id = ak.id
|
||||
LEFT JOIN api_key_tag_access AS at ON at.api_key_id = ak.id
|
||||
GROUP BY ak.id
|
||||
ORDER BY ak.is_active DESC, ak.updated_at DESC, ak.created_at DESC, ak.name COLLATE NOCASE`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query api keys: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]APIKeyListItem, 0)
|
||||
for rows.Next() {
|
||||
item, err := scanAPIKeyListItem(rows)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan api key list item: %w", err)
|
||||
}
|
||||
|
||||
items = append(items, *item)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate api keys: %w", err)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) GetByID(ctx context.Context, id int64) (*APIKey, error) {
|
||||
key, err := scanAPIKey(r.q.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
key_prefix,
|
||||
key_hash,
|
||||
description,
|
||||
scope_mode,
|
||||
can_download,
|
||||
can_upload,
|
||||
can_delete,
|
||||
can_manage_projects,
|
||||
is_active,
|
||||
expires_at,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_used_at,
|
||||
created_by_user_id
|
||||
FROM api_keys
|
||||
WHERE id = ?`,
|
||||
id,
|
||||
))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("scan api key by id: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) GetByHash(ctx context.Context, keyHash string) (*APIKey, error) {
|
||||
key, err := scanAPIKey(r.q.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
key_prefix,
|
||||
key_hash,
|
||||
description,
|
||||
scope_mode,
|
||||
can_download,
|
||||
can_upload,
|
||||
can_delete,
|
||||
can_manage_projects,
|
||||
is_active,
|
||||
expires_at,
|
||||
created_at,
|
||||
updated_at,
|
||||
last_used_at,
|
||||
created_by_user_id
|
||||
FROM api_keys
|
||||
WHERE key_hash = ?
|
||||
LIMIT 1`,
|
||||
strings.TrimSpace(keyHash),
|
||||
))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("scan api key by hash: %w", err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) Create(ctx context.Context, params CreateAPIKeyParams) (*APIKey, error) {
|
||||
isActive := 0
|
||||
if params.IsActive {
|
||||
isActive = 1
|
||||
}
|
||||
|
||||
result, err := r.q.ExecContext(
|
||||
ctx,
|
||||
`INSERT INTO api_keys (
|
||||
name,
|
||||
key_prefix,
|
||||
key_hash,
|
||||
description,
|
||||
scope_mode,
|
||||
can_download,
|
||||
can_upload,
|
||||
can_delete,
|
||||
can_manage_projects,
|
||||
is_active,
|
||||
expires_at,
|
||||
created_by_user_id
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
strings.TrimSpace(params.Name),
|
||||
strings.TrimSpace(params.KeyPrefix),
|
||||
strings.TrimSpace(params.KeyHash),
|
||||
strings.TrimSpace(params.Description),
|
||||
params.ScopeMode,
|
||||
boolToInt(params.CanDownload),
|
||||
boolToInt(params.CanUpload),
|
||||
boolToInt(params.CanDelete),
|
||||
boolToInt(params.CanManageProjects),
|
||||
isActive,
|
||||
nullableTimestampValue(params.ExpiresAt),
|
||||
params.CreatedByUserID,
|
||||
)
|
||||
if err != nil {
|
||||
if isUniqueConstraintError(err) {
|
||||
return nil, errors.Join(ErrConflict, fmt.Errorf("insert api key: %w", err))
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("insert api key: %w", err)
|
||||
}
|
||||
|
||||
apiKeyID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load inserted api key id: %w", err)
|
||||
}
|
||||
|
||||
return r.GetByID(ctx, apiKeyID)
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) Update(ctx context.Context, apiKeyID int64, params UpdateAPIKeyParams) (*APIKey, error) {
|
||||
result, err := r.q.ExecContext(
|
||||
ctx,
|
||||
`UPDATE api_keys
|
||||
SET name = ?,
|
||||
description = ?,
|
||||
scope_mode = ?,
|
||||
can_download = ?,
|
||||
can_upload = ?,
|
||||
can_delete = ?,
|
||||
can_manage_projects = ?,
|
||||
expires_at = ?
|
||||
WHERE id = ?`,
|
||||
strings.TrimSpace(params.Name),
|
||||
strings.TrimSpace(params.Description),
|
||||
params.ScopeMode,
|
||||
boolToInt(params.CanDownload),
|
||||
boolToInt(params.CanUpload),
|
||||
boolToInt(params.CanDelete),
|
||||
boolToInt(params.CanManageProjects),
|
||||
nullableTimestampValue(params.ExpiresAt),
|
||||
apiKeyID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update api key: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read updated api key rows: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return r.GetByID(ctx, apiKeyID)
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) SetActive(ctx context.Context, apiKeyID int64, isActive bool) error {
|
||||
result, err := r.q.ExecContext(
|
||||
ctx,
|
||||
`UPDATE api_keys SET is_active = ? WHERE id = ?`,
|
||||
boolToInt(isActive),
|
||||
apiKeyID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update api key active state: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read updated api key rows: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) TouchLastUsedAt(ctx context.Context, apiKeyID int64, usedAt time.Time) error {
|
||||
result, err := r.q.ExecContext(
|
||||
ctx,
|
||||
`UPDATE api_keys SET last_used_at = ? WHERE id = ?`,
|
||||
formatTimestamp(usedAt),
|
||||
apiKeyID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update api key last_used_at: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read updated api key rows: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ListProjectAccess(ctx context.Context, apiKeyID int64) ([]Project, error) {
|
||||
rows, err := r.q.QueryContext(
|
||||
ctx,
|
||||
`SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
p.slug,
|
||||
p.description,
|
||||
p.is_active,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM projects AS p
|
||||
INNER JOIN api_key_project_access AS ap ON ap.project_id = p.id
|
||||
WHERE ap.api_key_id = ?
|
||||
ORDER BY p.name COLLATE NOCASE`,
|
||||
apiKeyID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query api key project access: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
projects := make([]Project, 0)
|
||||
for rows.Next() {
|
||||
project, err := scanProject(rows)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan api key project access: %w", err)
|
||||
}
|
||||
|
||||
projects = append(projects, *project)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate api key project access: %w", err)
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ListTagAccess(ctx context.Context, apiKeyID int64) ([]Tag, error) {
|
||||
rows, err := r.q.QueryContext(
|
||||
ctx,
|
||||
`SELECT
|
||||
t.id,
|
||||
t.name,
|
||||
t.slug,
|
||||
t.description,
|
||||
t.created_at,
|
||||
t.updated_at
|
||||
FROM tags AS t
|
||||
INNER JOIN api_key_tag_access AS at ON at.tag_id = t.id
|
||||
WHERE at.api_key_id = ?
|
||||
ORDER BY t.name COLLATE NOCASE`,
|
||||
apiKeyID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query api key tag access: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tags := make([]Tag, 0)
|
||||
for rows.Next() {
|
||||
tag, err := scanTag(rows)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan api key tag access: %w", err)
|
||||
}
|
||||
|
||||
tags = append(tags, *tag)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate api key tag access: %w", err)
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ReplaceProjectAccess(ctx context.Context, apiKeyID int64, projectIDs []int64) error {
|
||||
if err := r.ClearProjectAccess(ctx, apiKeyID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, projectID := range normalizeIDList(projectIDs) {
|
||||
if _, err := r.q.ExecContext(
|
||||
ctx,
|
||||
`INSERT OR IGNORE INTO api_key_project_access (api_key_id, project_id) VALUES (?, ?)`,
|
||||
apiKeyID,
|
||||
projectID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("insert api key project access: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ReplaceTagAccess(ctx context.Context, apiKeyID int64, tagIDs []int64) error {
|
||||
if err := r.ClearTagAccess(ctx, apiKeyID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tagID := range normalizeIDList(tagIDs) {
|
||||
if _, err := r.q.ExecContext(
|
||||
ctx,
|
||||
`INSERT OR IGNORE INTO api_key_tag_access (api_key_id, tag_id) VALUES (?, ?)`,
|
||||
apiKeyID,
|
||||
tagID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("insert api key tag access: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ClearProjectAccess(ctx context.Context, apiKeyID int64) error {
|
||||
if _, err := r.q.ExecContext(ctx, `DELETE FROM api_key_project_access WHERE api_key_id = ?`, apiKeyID); err != nil {
|
||||
return fmt.Errorf("delete api key project access: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ClearTagAccess(ctx context.Context, apiKeyID int64) error {
|
||||
if _, err := r.q.ExecContext(ctx, `DELETE FROM api_key_tag_access WHERE api_key_id = ?`, apiKeyID); err != nil {
|
||||
return fmt.Errorf("delete api key tag access: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) ListAccessibleProjects(ctx context.Context, apiKeyID int64, scopeMode ScopeMode) ([]Project, error) {
|
||||
rows, err := r.q.QueryContext(
|
||||
ctx,
|
||||
`SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
p.slug,
|
||||
p.description,
|
||||
p.is_active,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM projects AS p
|
||||
WHERE p.is_active = 1 AND (
|
||||
? = 'all_projects'
|
||||
OR (
|
||||
? = 'project_allow_list'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM api_key_project_access AS ap
|
||||
WHERE ap.api_key_id = ?
|
||||
AND ap.project_id = p.id
|
||||
)
|
||||
)
|
||||
OR (
|
||||
? = 'project_deny_list'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM api_key_project_access AS ap
|
||||
WHERE ap.api_key_id = ?
|
||||
AND ap.project_id = p.id
|
||||
)
|
||||
)
|
||||
OR (
|
||||
? = 'tag_allow_list'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM project_tags AS pt
|
||||
INNER JOIN api_key_tag_access AS at ON at.tag_id = pt.tag_id
|
||||
WHERE at.api_key_id = ?
|
||||
AND pt.project_id = p.id
|
||||
)
|
||||
)
|
||||
OR (
|
||||
? = 'tag_deny_list'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM project_tags AS pt
|
||||
INNER JOIN api_key_tag_access AS at ON at.tag_id = pt.tag_id
|
||||
WHERE at.api_key_id = ?
|
||||
AND pt.project_id = p.id
|
||||
)
|
||||
)
|
||||
)
|
||||
ORDER BY p.name COLLATE NOCASE`,
|
||||
scopeMode,
|
||||
scopeMode, apiKeyID,
|
||||
scopeMode, apiKeyID,
|
||||
scopeMode, apiKeyID,
|
||||
scopeMode, apiKeyID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query accessible projects: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
projects := make([]Project, 0)
|
||||
for rows.Next() {
|
||||
project, err := scanProject(rows)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan accessible project: %w", err)
|
||||
}
|
||||
|
||||
projects = append(projects, *project)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("iterate accessible projects: %w", err)
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
func (r *APIKeyRepository) HasProjectAccess(ctx context.Context, apiKeyID int64, scopeMode ScopeMode, projectID int64) (bool, error) {
|
||||
var exists int
|
||||
if err := r.q.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM projects AS p
|
||||
WHERE p.id = ?
|
||||
AND p.is_active = 1
|
||||
AND (
|
||||
? = 'all_projects'
|
||||
OR (
|
||||
? = 'project_allow_list'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM api_key_project_access AS ap
|
||||
WHERE ap.api_key_id = ?
|
||||
AND ap.project_id = p.id
|
||||
)
|
||||
)
|
||||
OR (
|
||||
? = 'project_deny_list'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM api_key_project_access AS ap
|
||||
WHERE ap.api_key_id = ?
|
||||
AND ap.project_id = p.id
|
||||
)
|
||||
)
|
||||
OR (
|
||||
? = 'tag_allow_list'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM project_tags AS pt
|
||||
INNER JOIN api_key_tag_access AS at ON at.tag_id = pt.tag_id
|
||||
WHERE at.api_key_id = ?
|
||||
AND pt.project_id = p.id
|
||||
)
|
||||
)
|
||||
OR (
|
||||
? = 'tag_deny_list'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM project_tags AS pt
|
||||
INNER JOIN api_key_tag_access AS at ON at.tag_id = pt.tag_id
|
||||
WHERE at.api_key_id = ?
|
||||
AND pt.project_id = p.id
|
||||
)
|
||||
)
|
||||
)
|
||||
)`,
|
||||
projectID,
|
||||
scopeMode,
|
||||
scopeMode, apiKeyID,
|
||||
scopeMode, apiKeyID,
|
||||
scopeMode, apiKeyID,
|
||||
scopeMode, apiKeyID,
|
||||
).Scan(&exists); err != nil {
|
||||
return false, fmt.Errorf("query api key project access: %w", err)
|
||||
}
|
||||
|
||||
return exists == 1, nil
|
||||
}
|
||||
|
||||
func scanAPIKey(scanner rowScanner) (*APIKey, error) {
|
||||
var (
|
||||
key APIKey
|
||||
scopeMode string
|
||||
canDownload int
|
||||
canUpload int
|
||||
canDelete int
|
||||
canManageProjects int
|
||||
isActive int
|
||||
expiresAtRaw sql.NullString
|
||||
createdAtRaw string
|
||||
updatedAtRaw string
|
||||
lastUsedAtRaw sql.NullString
|
||||
createdByUserIDRaw sql.NullInt64
|
||||
)
|
||||
|
||||
if err := scanner.Scan(
|
||||
&key.ID,
|
||||
&key.Name,
|
||||
&key.KeyPrefix,
|
||||
&key.KeyHash,
|
||||
&key.Description,
|
||||
&scopeMode,
|
||||
&canDownload,
|
||||
&canUpload,
|
||||
&canDelete,
|
||||
&canManageProjects,
|
||||
&isActive,
|
||||
&expiresAtRaw,
|
||||
&createdAtRaw,
|
||||
&updatedAtRaw,
|
||||
&lastUsedAtRaw,
|
||||
&createdByUserIDRaw,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdAt, err := parseTimestamp(createdAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key created_at: %w", err)
|
||||
}
|
||||
|
||||
updatedAt, err := parseTimestamp(updatedAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key updated_at: %w", err)
|
||||
}
|
||||
|
||||
expiresAt, err := parseNullableTimestamp(expiresAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key expires_at: %w", err)
|
||||
}
|
||||
|
||||
lastUsedAt, err := parseNullableTimestamp(lastUsedAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key last_used_at: %w", err)
|
||||
}
|
||||
|
||||
key.ScopeMode = ScopeMode(scopeMode)
|
||||
key.CanDownload = canDownload == 1
|
||||
key.CanUpload = canUpload == 1
|
||||
key.CanDelete = canDelete == 1
|
||||
key.CanManageProjects = canManageProjects == 1
|
||||
key.IsActive = isActive == 1
|
||||
key.ExpiresAt = expiresAt
|
||||
key.CreatedAt = createdAt
|
||||
key.UpdatedAt = updatedAt
|
||||
key.LastUsedAt = lastUsedAt
|
||||
if createdByUserIDRaw.Valid {
|
||||
key.CreatedByUserID = &createdByUserIDRaw.Int64
|
||||
}
|
||||
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
func scanAPIKeyListItem(scanner rowScanner) (*APIKeyListItem, error) {
|
||||
var (
|
||||
item APIKeyListItem
|
||||
scopeMode string
|
||||
canDownload int
|
||||
canUpload int
|
||||
canDelete int
|
||||
canManageProjects int
|
||||
isActive int
|
||||
expiresAtRaw sql.NullString
|
||||
createdAtRaw string
|
||||
updatedAtRaw string
|
||||
lastUsedAtRaw sql.NullString
|
||||
createdByUserIDRaw sql.NullInt64
|
||||
)
|
||||
|
||||
if err := scanner.Scan(
|
||||
&item.APIKey.ID,
|
||||
&item.APIKey.Name,
|
||||
&item.APIKey.KeyPrefix,
|
||||
&item.APIKey.KeyHash,
|
||||
&item.APIKey.Description,
|
||||
&scopeMode,
|
||||
&canDownload,
|
||||
&canUpload,
|
||||
&canDelete,
|
||||
&canManageProjects,
|
||||
&isActive,
|
||||
&expiresAtRaw,
|
||||
&createdAtRaw,
|
||||
&updatedAtRaw,
|
||||
&lastUsedAtRaw,
|
||||
&createdByUserIDRaw,
|
||||
&item.ProjectRuleCount,
|
||||
&item.TagRuleCount,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdAt, err := parseTimestamp(createdAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key list created_at: %w", err)
|
||||
}
|
||||
|
||||
updatedAt, err := parseTimestamp(updatedAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key list updated_at: %w", err)
|
||||
}
|
||||
|
||||
expiresAt, err := parseNullableTimestamp(expiresAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key list expires_at: %w", err)
|
||||
}
|
||||
|
||||
lastUsedAt, err := parseNullableTimestamp(lastUsedAtRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse api key list last_used_at: %w", err)
|
||||
}
|
||||
|
||||
item.APIKey.ScopeMode = ScopeMode(scopeMode)
|
||||
item.APIKey.CanDownload = canDownload == 1
|
||||
item.APIKey.CanUpload = canUpload == 1
|
||||
item.APIKey.CanDelete = canDelete == 1
|
||||
item.APIKey.CanManageProjects = canManageProjects == 1
|
||||
item.APIKey.IsActive = isActive == 1
|
||||
item.APIKey.ExpiresAt = expiresAt
|
||||
item.APIKey.CreatedAt = createdAt
|
||||
item.APIKey.UpdatedAt = updatedAt
|
||||
item.APIKey.LastUsedAt = lastUsedAt
|
||||
if createdByUserIDRaw.Valid {
|
||||
item.APIKey.CreatedByUserID = &createdByUserIDRaw.Int64
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func boolToInt(value bool) int {
|
||||
if value {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func nullableTimestampValue(value *time.Time) any {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return formatTimestamp(value.UTC())
|
||||
}
|
||||
|
||||
func normalizeIDList(values []int64) []int64 {
|
||||
seen := make(map[int64]struct{}, len(values))
|
||||
normalized := make([]int64, 0, len(values))
|
||||
|
||||
for _, value := range values {
|
||||
if value <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := seen[value]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[value] = struct{}{}
|
||||
normalized = append(normalized, value)
|
||||
}
|
||||
|
||||
sort.Slice(normalized, func(i, j int) bool {
|
||||
return normalized[i] < normalized[j]
|
||||
})
|
||||
|
||||
return normalized
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue