761 lines
17 KiB
Go
761 lines
17 KiB
Go
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
|
|
}
|