303 lines
7.0 KiB
Go
303 lines
7.0 KiB
Go
package basemap
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"map-asset-gateway/api-go/internal/uid"
|
|
)
|
|
|
|
func (s *Store) EnsureScanSource(ctx context.Context, input CreateScanSourceInput) (ScanSource, error) {
|
|
code := normalizeBasemapCode(input.Code)
|
|
if code == "" {
|
|
return ScanSource{}, errors.New("scan source code is required")
|
|
}
|
|
name := strings.TrimSpace(input.Name)
|
|
if name == "" {
|
|
name = code
|
|
}
|
|
rootPath := strings.TrimSpace(input.RootPath)
|
|
if rootPath == "" {
|
|
return ScanSource{}, errors.New("scan source root path is required")
|
|
}
|
|
absRoot, err := filepath.Abs(rootPath)
|
|
if err != nil {
|
|
return ScanSource{}, fmt.Errorf("resolve root path: %w", err)
|
|
}
|
|
manifestName := strings.TrimSpace(input.ManifestName)
|
|
if manifestName == "" {
|
|
manifestName = defaultManifestName
|
|
}
|
|
|
|
now := nowUTC()
|
|
id := uid.Deterministic("scan-source", code)
|
|
_, err = s.db.ExecContext(ctx, `
|
|
INSERT INTO scan_sources (
|
|
id, code, name, root_path, manifest_name, enabled, metadata_json, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, 1, '', ?, ?)
|
|
ON CONFLICT(code) DO UPDATE SET
|
|
name = excluded.name,
|
|
root_path = excluded.root_path,
|
|
manifest_name = excluded.manifest_name,
|
|
enabled = 1,
|
|
updated_at = excluded.updated_at
|
|
`, id, code, name, absRoot, manifestName, toRFC3339(now), toRFC3339(now))
|
|
if err != nil {
|
|
return ScanSource{}, fmt.Errorf("upsert scan source: %w", err)
|
|
}
|
|
return s.GetScanSourceByCode(ctx, code)
|
|
}
|
|
|
|
func (s *Store) GetScanSourceByCode(ctx context.Context, code string) (ScanSource, error) {
|
|
row := s.db.QueryRowContext(ctx, `
|
|
SELECT id, code, name, root_path, manifest_name, enabled, metadata_json, created_at, updated_at
|
|
FROM scan_sources
|
|
WHERE code = ?
|
|
`, normalizeBasemapCode(code))
|
|
item, err := scanScanSource(row)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return ScanSource{}, fmt.Errorf("scan source %q not found", code)
|
|
}
|
|
return ScanSource{}, err
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
func (s *Store) ListScanSources(ctx context.Context) ([]ScanSource, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT id, code, name, root_path, manifest_name, enabled, metadata_json, created_at, updated_at
|
|
FROM scan_sources
|
|
ORDER BY code
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []ScanSource
|
|
for rows.Next() {
|
|
item, err := scanScanSource(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, rows.Err()
|
|
}
|
|
|
|
func (s *Store) ListBasemaps(ctx context.Context) ([]Basemap, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT id, code, name, type, status, description, created_at, updated_at
|
|
FROM basemaps
|
|
ORDER BY code
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []Basemap
|
|
indexByID := map[string]int{}
|
|
for rows.Next() {
|
|
item, err := scanBasemap(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
indexByID[item.ID] = len(items)
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
versionRows, err := s.db.QueryContext(ctx, `
|
|
SELECT
|
|
v.id,
|
|
v.basemap_id,
|
|
b.code,
|
|
v.version,
|
|
v.status,
|
|
v.is_default,
|
|
v.manifest_path,
|
|
v.tile_root_path,
|
|
v.url_template,
|
|
v.tile_format,
|
|
v.tile_scheme,
|
|
v.min_zoom,
|
|
v.max_zoom,
|
|
v.bbox_json,
|
|
v.attribution,
|
|
v.metadata_json,
|
|
v.created_at,
|
|
v.updated_at
|
|
FROM basemap_versions v
|
|
JOIN basemaps b ON b.id = v.basemap_id
|
|
ORDER BY b.code, v.version
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer versionRows.Close()
|
|
|
|
for versionRows.Next() {
|
|
row, err := scanBasemapVersionRow(versionRows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
index, ok := indexByID[row.BasemapID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
version := decodeBasemapVersion(row)
|
|
items[index].Versions = append(items[index].Versions, version)
|
|
if version.IsDefault {
|
|
copyValue := version
|
|
items[index].Default = ©Value
|
|
}
|
|
}
|
|
if err := versionRows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (s *Store) GetBasemapByCode(ctx context.Context, code string) (Basemap, error) {
|
|
items, err := s.ListBasemaps(ctx)
|
|
if err != nil {
|
|
return Basemap{}, err
|
|
}
|
|
normalized := normalizeBasemapCode(code)
|
|
for _, item := range items {
|
|
if item.Code == normalized {
|
|
return item, nil
|
|
}
|
|
}
|
|
return Basemap{}, fmt.Errorf("basemap %q not found", code)
|
|
}
|
|
|
|
func (s *Store) SetDefaultVersion(ctx context.Context, basemapCode, version string) error {
|
|
basemapCode = normalizeBasemapCode(basemapCode)
|
|
version = strings.TrimSpace(version)
|
|
if basemapCode == "" || version == "" {
|
|
return errors.New("basemap code and version are required")
|
|
}
|
|
tx, err := s.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var basemapID string
|
|
if err := tx.QueryRowContext(ctx, `SELECT id FROM basemaps WHERE code = ?`, basemapCode).Scan(&basemapID); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return fmt.Errorf("basemap %q not found", basemapCode)
|
|
}
|
|
return err
|
|
}
|
|
result, err := tx.ExecContext(ctx, `
|
|
UPDATE basemap_versions
|
|
SET is_default = CASE WHEN version = ? THEN 1 ELSE 0 END, updated_at = ?
|
|
WHERE basemap_id = ?
|
|
`, version, toRFC3339(nowUTC()), basemapID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
count, err := result.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count == 0 {
|
|
return fmt.Errorf("version %q not found for basemap %q", version, basemapCode)
|
|
}
|
|
if _, err := tx.ExecContext(ctx, `UPDATE basemaps SET updated_at = ? WHERE id = ?`, toRFC3339(nowUTC()), basemapID); err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
func (s *Store) ListScanRuns(ctx context.Context, limit int) ([]ScanRun, error) {
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT
|
|
r.id,
|
|
r.scan_source_id,
|
|
s.code,
|
|
r.status,
|
|
r.scanned_count,
|
|
r.added_count,
|
|
r.updated_count,
|
|
r.removed_count,
|
|
r.summary_json,
|
|
r.started_at,
|
|
r.finished_at
|
|
FROM scan_runs r
|
|
JOIN scan_sources s ON s.id = r.scan_source_id
|
|
ORDER BY r.started_at DESC
|
|
LIMIT ?
|
|
`, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []ScanRun
|
|
for rows.Next() {
|
|
item, err := scanScanRun(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, rows.Err()
|
|
}
|
|
|
|
func (s *Store) ListPushRecords(ctx context.Context, limit int) ([]PushRecord, error) {
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT
|
|
p.id,
|
|
p.target_system_id,
|
|
t.code,
|
|
p.basemap_version_id,
|
|
b.code,
|
|
v.version,
|
|
p.status,
|
|
p.request_json,
|
|
p.response_status,
|
|
p.response_body,
|
|
p.error_message,
|
|
p.pushed_at,
|
|
p.finished_at
|
|
FROM push_records p
|
|
JOIN target_systems t ON t.id = p.target_system_id
|
|
JOIN basemap_versions v ON v.id = p.basemap_version_id
|
|
JOIN basemaps b ON b.id = v.basemap_id
|
|
ORDER BY p.pushed_at DESC
|
|
LIMIT ?
|
|
`, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var items []PushRecord
|
|
for rows.Next() {
|
|
item, err := scanPushRecord(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, rows.Err()
|
|
}
|