285 lines
5.9 KiB
Go
285 lines
5.9 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type CreateReleaseParams struct {
|
|
ProjectID int64
|
|
Version string
|
|
Build string
|
|
Filename string
|
|
StoragePath string
|
|
ChecksumSHA256 string
|
|
SizeBytes int64
|
|
ContentType string
|
|
ReleaseNotes string
|
|
UploadedByUserID *int64
|
|
IsActive bool
|
|
}
|
|
|
|
func (r *ReleaseRepository) Create(ctx context.Context, params CreateReleaseParams) (*Release, error) {
|
|
isActive := 0
|
|
if params.IsActive {
|
|
isActive = 1
|
|
}
|
|
|
|
result, err := r.q.ExecContext(
|
|
ctx,
|
|
`INSERT INTO releases (
|
|
project_id,
|
|
version,
|
|
build,
|
|
filename,
|
|
storage_path,
|
|
checksum_sha256,
|
|
size_bytes,
|
|
content_type,
|
|
release_notes,
|
|
uploaded_by_user_id,
|
|
is_active
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
params.ProjectID,
|
|
strings.TrimSpace(params.Version),
|
|
strings.TrimSpace(params.Build),
|
|
strings.TrimSpace(params.Filename),
|
|
strings.TrimSpace(params.StoragePath),
|
|
strings.TrimSpace(params.ChecksumSHA256),
|
|
params.SizeBytes,
|
|
strings.TrimSpace(params.ContentType),
|
|
strings.TrimSpace(params.ReleaseNotes),
|
|
params.UploadedByUserID,
|
|
isActive,
|
|
)
|
|
if err != nil {
|
|
if isUniqueConstraintError(err) {
|
|
return nil, errors.Join(ErrConflict, fmt.Errorf("insert release: %w", err))
|
|
}
|
|
|
|
return nil, fmt.Errorf("insert release: %w", err)
|
|
}
|
|
|
|
releaseID, err := result.LastInsertId()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load inserted release id: %w", err)
|
|
}
|
|
|
|
return r.GetByID(ctx, releaseID)
|
|
}
|
|
|
|
func (r *ReleaseRepository) GetByID(ctx context.Context, id int64) (*Release, error) {
|
|
release, err := scanRelease(r.q.QueryRowContext(
|
|
ctx,
|
|
`SELECT
|
|
id,
|
|
project_id,
|
|
version,
|
|
build,
|
|
filename,
|
|
storage_path,
|
|
checksum_sha256,
|
|
size_bytes,
|
|
content_type,
|
|
release_notes,
|
|
created_at,
|
|
updated_at,
|
|
uploaded_by_user_id,
|
|
is_active
|
|
FROM releases
|
|
WHERE id = ?`,
|
|
id,
|
|
))
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return nil, fmt.Errorf("scan release by id: %w", err)
|
|
}
|
|
|
|
return release, nil
|
|
}
|
|
|
|
func (r *ReleaseRepository) GetLatestByProjectID(ctx context.Context, projectID int64) (*Release, error) {
|
|
release, err := scanRelease(r.q.QueryRowContext(
|
|
ctx,
|
|
`SELECT
|
|
id,
|
|
project_id,
|
|
version,
|
|
build,
|
|
filename,
|
|
storage_path,
|
|
checksum_sha256,
|
|
size_bytes,
|
|
content_type,
|
|
release_notes,
|
|
created_at,
|
|
updated_at,
|
|
uploaded_by_user_id,
|
|
is_active
|
|
FROM releases
|
|
WHERE project_id = ?
|
|
AND is_active = 1
|
|
ORDER BY created_at DESC, id DESC
|
|
LIMIT 1`,
|
|
projectID,
|
|
))
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
return nil, fmt.Errorf("scan latest release by project id: %w", err)
|
|
}
|
|
|
|
return release, nil
|
|
}
|
|
|
|
func (r *ReleaseRepository) ListByProjectID(ctx context.Context, projectID int64) ([]ReleaseListItem, error) {
|
|
rows, err := r.q.QueryContext(
|
|
ctx,
|
|
`SELECT
|
|
r.id,
|
|
r.project_id,
|
|
r.version,
|
|
r.build,
|
|
r.filename,
|
|
r.storage_path,
|
|
r.checksum_sha256,
|
|
r.size_bytes,
|
|
r.content_type,
|
|
r.release_notes,
|
|
r.created_at,
|
|
r.updated_at,
|
|
r.uploaded_by_user_id,
|
|
r.is_active,
|
|
COALESCE(u.email, '')
|
|
FROM releases AS r
|
|
LEFT JOIN users AS u ON u.id = r.uploaded_by_user_id
|
|
WHERE r.project_id = ?
|
|
ORDER BY r.created_at DESC, r.id DESC`,
|
|
projectID,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("query project releases: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
releases := make([]ReleaseListItem, 0)
|
|
for rows.Next() {
|
|
item, err := scanReleaseListItem(rows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scan project release: %w", err)
|
|
}
|
|
|
|
releases = append(releases, *item)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate project releases: %w", err)
|
|
}
|
|
|
|
return releases, nil
|
|
}
|
|
|
|
func scanRelease(scanner rowScanner) (*Release, error) {
|
|
var (
|
|
release Release
|
|
createdAtRaw string
|
|
updatedAtRaw string
|
|
uploadedByUserID sql.NullInt64
|
|
isActive int
|
|
)
|
|
|
|
if err := scanner.Scan(
|
|
&release.ID,
|
|
&release.ProjectID,
|
|
&release.Version,
|
|
&release.Build,
|
|
&release.Filename,
|
|
&release.StoragePath,
|
|
&release.ChecksumSHA256,
|
|
&release.SizeBytes,
|
|
&release.ContentType,
|
|
&release.ReleaseNotes,
|
|
&createdAtRaw,
|
|
&updatedAtRaw,
|
|
&uploadedByUserID,
|
|
&isActive,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
createdAt, err := parseTimestamp(createdAtRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse release created_at: %w", err)
|
|
}
|
|
|
|
updatedAt, err := parseTimestamp(updatedAtRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse release updated_at: %w", err)
|
|
}
|
|
|
|
release.CreatedAt = createdAt
|
|
release.UpdatedAt = updatedAt
|
|
release.IsActive = isActive == 1
|
|
if uploadedByUserID.Valid {
|
|
release.UploadedByUserID = &uploadedByUserID.Int64
|
|
}
|
|
|
|
return &release, nil
|
|
}
|
|
|
|
func scanReleaseListItem(scanner rowScanner) (*ReleaseListItem, error) {
|
|
var (
|
|
item ReleaseListItem
|
|
createdAtRaw string
|
|
updatedAtRaw string
|
|
uploadedByUserID sql.NullInt64
|
|
isActive int
|
|
)
|
|
|
|
if err := scanner.Scan(
|
|
&item.Release.ID,
|
|
&item.Release.ProjectID,
|
|
&item.Release.Version,
|
|
&item.Release.Build,
|
|
&item.Release.Filename,
|
|
&item.Release.StoragePath,
|
|
&item.Release.ChecksumSHA256,
|
|
&item.Release.SizeBytes,
|
|
&item.Release.ContentType,
|
|
&item.Release.ReleaseNotes,
|
|
&createdAtRaw,
|
|
&updatedAtRaw,
|
|
&uploadedByUserID,
|
|
&isActive,
|
|
&item.UploadedByEmail,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
createdAt, err := parseTimestamp(createdAtRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse release list created_at: %w", err)
|
|
}
|
|
|
|
updatedAt, err := parseTimestamp(updatedAtRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse release list updated_at: %w", err)
|
|
}
|
|
|
|
item.Release.CreatedAt = createdAt
|
|
item.Release.UpdatedAt = updatedAt
|
|
item.Release.IsActive = isActive == 1
|
|
if uploadedByUserID.Valid {
|
|
item.Release.UploadedByUserID = &uploadedByUserID.Int64
|
|
}
|
|
|
|
return &item, nil
|
|
}
|