refactor: content-addressed assets, cross-channel multimodal, infra simplification (#63)

* refactor(attachment): multimodal attachment refactor with snapshot schema and storage layer

- Add snapshot schema migration (0008) and update init/versions/snapshots
- Add internal/attachment and internal/channel normalize for unified attachment handling
- Move containerfs provider from internal/media to internal/storage
- Update agent types, channel adapters (Telegram/Feishu), inbound and handlers
- Add containerd snapshot lineage and local_channel tests
- Regenerate sqlc, swagger and SDK

* refactor(media): content-addressed asset system with unified naming

- Replace asset_id foreign key with content_hash as sole identifier
  for bot_history_message_assets (pure soft-link model)
- Remove mime, size_bytes, storage_key from DB; derive at read time
  via media.Resolve from actual storage
- Merge migrations 0008/0009 into single 0008; keep 0001 as canonical schema
- Add Docker initdb script for deterministic migration execution order
- Fix cross-channel real-time image display (Telegram → WebUI SSE)
- Fix message disappearing on refresh (null assets fallback)
- Fix file icon instead of image preview (mime derivation from storage)
- Unify AssetID → ContentHash naming across Go, Agent, and Frontend
- Change storage key prefix from 4-char to 2-char for directory sharding
- Add server-entrypoint.sh for Docker deployment migration handling

* refactor(infra): embedded migrations, Docker simplification, and config consolidation

- Embed SQL migrations into Go binary, removing shell-based migration scripts
- Consolidate config files into conf/ directory (app.example.toml, app.docker.toml, app.dev.toml)
- Simplify Docker setup: remove initdb.d scripts, streamline nginx config and entrypoint
- Remove legacy CLI, feishu-echo commands, and obsolete incremental migration files
- Update install script and docs to require sudo for one-click install
- Add mise tasks for dev environment orchestration

* chore: recover migrations

---------

Co-authored-by: Acbox <acbox0328@gmail.com>
This commit is contained in:
BBQ
2026-02-19 00:20:27 +08:00
committed by GitHub
parent 740f620fe4
commit bc374fe8cd
104 changed files with 6133 additions and 2987 deletions
+32 -275
View File
@@ -11,110 +11,41 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const createMediaAsset = `-- name: CreateMediaAsset :one
INSERT INTO media_assets (
bot_id, storage_provider_id, content_hash, media_type, mime,
size_bytes, storage_key, original_name, width, height, duration_ms, metadata
)
const createMessageAsset = `-- name: CreateMessageAsset :one
INSERT INTO bot_history_message_assets (message_id, role, ordinal, content_hash)
VALUES (
$1,
$2::uuid,
$2,
$3,
$4,
$5,
$6,
$7,
$8::text,
$9::integer,
$10::integer,
$11::bigint,
$12
$4
)
ON CONFLICT (bot_id, content_hash) DO UPDATE SET
bot_id = media_assets.bot_id
RETURNING id, bot_id, storage_provider_id, content_hash, media_type, mime, size_bytes, storage_key, original_name, width, height, duration_ms, metadata, created_at
`
type CreateMediaAssetParams struct {
BotID pgtype.UUID `json:"bot_id"`
StorageProviderID pgtype.UUID `json:"storage_provider_id"`
ContentHash string `json:"content_hash"`
MediaType string `json:"media_type"`
Mime string `json:"mime"`
SizeBytes int64 `json:"size_bytes"`
StorageKey string `json:"storage_key"`
OriginalName pgtype.Text `json:"original_name"`
Width pgtype.Int4 `json:"width"`
Height pgtype.Int4 `json:"height"`
DurationMs pgtype.Int8 `json:"duration_ms"`
Metadata []byte `json:"metadata"`
}
func (q *Queries) CreateMediaAsset(ctx context.Context, arg CreateMediaAssetParams) (MediaAsset, error) {
row := q.db.QueryRow(ctx, createMediaAsset,
arg.BotID,
arg.StorageProviderID,
arg.ContentHash,
arg.MediaType,
arg.Mime,
arg.SizeBytes,
arg.StorageKey,
arg.OriginalName,
arg.Width,
arg.Height,
arg.DurationMs,
arg.Metadata,
)
var i MediaAsset
err := row.Scan(
&i.ID,
&i.BotID,
&i.StorageProviderID,
&i.ContentHash,
&i.MediaType,
&i.Mime,
&i.SizeBytes,
&i.StorageKey,
&i.OriginalName,
&i.Width,
&i.Height,
&i.DurationMs,
&i.Metadata,
&i.CreatedAt,
)
return i, err
}
const createMessageAsset = `-- name: CreateMessageAsset :one
INSERT INTO bot_history_message_assets (message_id, asset_id, role, ordinal)
VALUES ($1, $2, $3, $4)
ON CONFLICT (message_id, asset_id) DO UPDATE SET
ON CONFLICT (message_id, content_hash) DO UPDATE SET
role = EXCLUDED.role,
ordinal = EXCLUDED.ordinal
RETURNING id, message_id, asset_id, role, ordinal, created_at
RETURNING id, message_id, role, ordinal, content_hash, created_at
`
type CreateMessageAssetParams struct {
MessageID pgtype.UUID `json:"message_id"`
AssetID pgtype.UUID `json:"asset_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
MessageID pgtype.UUID `json:"message_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
ContentHash string `json:"content_hash"`
}
func (q *Queries) CreateMessageAsset(ctx context.Context, arg CreateMessageAssetParams) (BotHistoryMessageAsset, error) {
row := q.db.QueryRow(ctx, createMessageAsset,
arg.MessageID,
arg.AssetID,
arg.Role,
arg.Ordinal,
arg.ContentHash,
)
var i BotHistoryMessageAsset
err := row.Scan(
&i.ID,
&i.MessageID,
&i.AssetID,
&i.Role,
&i.Ordinal,
&i.ContentHash,
&i.CreatedAt,
)
return i, err
@@ -146,15 +77,6 @@ func (q *Queries) CreateStorageProvider(ctx context.Context, arg CreateStoragePr
return i, err
}
const deleteMediaAsset = `-- name: DeleteMediaAsset :exec
DELETE FROM media_assets WHERE id = $1
`
func (q *Queries) DeleteMediaAsset(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteMediaAsset, id)
return err
}
const deleteMessageAssets = `-- name: DeleteMessageAssets :exec
DELETE FROM bot_history_message_assets WHERE message_id = $1
`
@@ -182,64 +104,6 @@ func (q *Queries) GetBotStorageBinding(ctx context.Context, botID pgtype.UUID) (
return i, err
}
const getMediaAssetByHash = `-- name: GetMediaAssetByHash :one
SELECT id, bot_id, storage_provider_id, content_hash, media_type, mime, size_bytes, storage_key, original_name, width, height, duration_ms, metadata, created_at FROM media_assets
WHERE bot_id = $1 AND content_hash = $2
`
type GetMediaAssetByHashParams struct {
BotID pgtype.UUID `json:"bot_id"`
ContentHash string `json:"content_hash"`
}
func (q *Queries) GetMediaAssetByHash(ctx context.Context, arg GetMediaAssetByHashParams) (MediaAsset, error) {
row := q.db.QueryRow(ctx, getMediaAssetByHash, arg.BotID, arg.ContentHash)
var i MediaAsset
err := row.Scan(
&i.ID,
&i.BotID,
&i.StorageProviderID,
&i.ContentHash,
&i.MediaType,
&i.Mime,
&i.SizeBytes,
&i.StorageKey,
&i.OriginalName,
&i.Width,
&i.Height,
&i.DurationMs,
&i.Metadata,
&i.CreatedAt,
)
return i, err
}
const getMediaAssetByID = `-- name: GetMediaAssetByID :one
SELECT id, bot_id, storage_provider_id, content_hash, media_type, mime, size_bytes, storage_key, original_name, width, height, duration_ms, metadata, created_at FROM media_assets WHERE id = $1
`
func (q *Queries) GetMediaAssetByID(ctx context.Context, id pgtype.UUID) (MediaAsset, error) {
row := q.db.QueryRow(ctx, getMediaAssetByID, id)
var i MediaAsset
err := row.Scan(
&i.ID,
&i.BotID,
&i.StorageProviderID,
&i.ContentHash,
&i.MediaType,
&i.Mime,
&i.SizeBytes,
&i.StorageKey,
&i.OriginalName,
&i.Width,
&i.Height,
&i.DurationMs,
&i.Metadata,
&i.CreatedAt,
)
return i, err
}
const getStorageProviderByID = `-- name: GetStorageProviderByID :one
SELECT id, name, provider, config, created_at, updated_at FROM storage_providers WHERE id = $1
`
@@ -276,84 +140,19 @@ func (q *Queries) GetStorageProviderByName(ctx context.Context, name string) (St
return i, err
}
const listMediaAssetsByBotID = `-- name: ListMediaAssetsByBotID :many
SELECT id, bot_id, storage_provider_id, content_hash, media_type, mime, size_bytes, storage_key, original_name, width, height, duration_ms, metadata, created_at FROM media_assets
WHERE bot_id = $1
ORDER BY created_at DESC
`
func (q *Queries) ListMediaAssetsByBotID(ctx context.Context, botID pgtype.UUID) ([]MediaAsset, error) {
rows, err := q.db.Query(ctx, listMediaAssetsByBotID, botID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []MediaAsset
for rows.Next() {
var i MediaAsset
if err := rows.Scan(
&i.ID,
&i.BotID,
&i.StorageProviderID,
&i.ContentHash,
&i.MediaType,
&i.Mime,
&i.SizeBytes,
&i.StorageKey,
&i.OriginalName,
&i.Width,
&i.Height,
&i.DurationMs,
&i.Metadata,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listMessageAssets = `-- name: ListMessageAssets :many
SELECT
ma.id AS rel_id,
ma.message_id,
ma.asset_id,
ma.role,
ma.ordinal,
a.media_type,
a.mime,
a.size_bytes,
a.storage_key,
a.original_name,
a.width,
a.height,
a.duration_ms,
a.metadata AS asset_metadata
FROM bot_history_message_assets ma
JOIN media_assets a ON a.id = ma.asset_id
WHERE ma.message_id = $1
ORDER BY ma.ordinal ASC
SELECT id AS rel_id, message_id, role, ordinal, content_hash
FROM bot_history_message_assets
WHERE message_id = $1
ORDER BY ordinal ASC
`
type ListMessageAssetsRow struct {
RelID pgtype.UUID `json:"rel_id"`
MessageID pgtype.UUID `json:"message_id"`
AssetID pgtype.UUID `json:"asset_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
MediaType string `json:"media_type"`
Mime string `json:"mime"`
SizeBytes int64 `json:"size_bytes"`
StorageKey string `json:"storage_key"`
OriginalName pgtype.Text `json:"original_name"`
Width pgtype.Int4 `json:"width"`
Height pgtype.Int4 `json:"height"`
DurationMs pgtype.Int8 `json:"duration_ms"`
AssetMetadata []byte `json:"asset_metadata"`
RelID pgtype.UUID `json:"rel_id"`
MessageID pgtype.UUID `json:"message_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
ContentHash string `json:"content_hash"`
}
func (q *Queries) ListMessageAssets(ctx context.Context, messageID pgtype.UUID) ([]ListMessageAssetsRow, error) {
@@ -368,18 +167,9 @@ func (q *Queries) ListMessageAssets(ctx context.Context, messageID pgtype.UUID)
if err := rows.Scan(
&i.RelID,
&i.MessageID,
&i.AssetID,
&i.Role,
&i.Ordinal,
&i.MediaType,
&i.Mime,
&i.SizeBytes,
&i.StorageKey,
&i.OriginalName,
&i.Width,
&i.Height,
&i.DurationMs,
&i.AssetMetadata,
&i.ContentHash,
); err != nil {
return nil, err
}
@@ -392,42 +182,18 @@ func (q *Queries) ListMessageAssets(ctx context.Context, messageID pgtype.UUID)
}
const listMessageAssetsBatch = `-- name: ListMessageAssetsBatch :many
SELECT
ma.id AS rel_id,
ma.message_id,
ma.asset_id,
ma.role,
ma.ordinal,
a.media_type,
a.mime,
a.size_bytes,
a.storage_key,
a.original_name,
a.width,
a.height,
a.duration_ms,
a.metadata AS asset_metadata
FROM bot_history_message_assets ma
JOIN media_assets a ON a.id = ma.asset_id
WHERE ma.message_id = ANY($1::uuid[])
ORDER BY ma.message_id, ma.ordinal ASC
SELECT id AS rel_id, message_id, role, ordinal, content_hash
FROM bot_history_message_assets
WHERE message_id = ANY($1::uuid[])
ORDER BY message_id, ordinal ASC
`
type ListMessageAssetsBatchRow struct {
RelID pgtype.UUID `json:"rel_id"`
MessageID pgtype.UUID `json:"message_id"`
AssetID pgtype.UUID `json:"asset_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
MediaType string `json:"media_type"`
Mime string `json:"mime"`
SizeBytes int64 `json:"size_bytes"`
StorageKey string `json:"storage_key"`
OriginalName pgtype.Text `json:"original_name"`
Width pgtype.Int4 `json:"width"`
Height pgtype.Int4 `json:"height"`
DurationMs pgtype.Int8 `json:"duration_ms"`
AssetMetadata []byte `json:"asset_metadata"`
RelID pgtype.UUID `json:"rel_id"`
MessageID pgtype.UUID `json:"message_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
ContentHash string `json:"content_hash"`
}
func (q *Queries) ListMessageAssetsBatch(ctx context.Context, messageIds []pgtype.UUID) ([]ListMessageAssetsBatchRow, error) {
@@ -442,18 +208,9 @@ func (q *Queries) ListMessageAssetsBatch(ctx context.Context, messageIds []pgtyp
if err := rows.Scan(
&i.RelID,
&i.MessageID,
&i.AssetID,
&i.Role,
&i.Ordinal,
&i.MediaType,
&i.Mime,
&i.SizeBytes,
&i.StorageKey,
&i.OriginalName,
&i.Width,
&i.Height,
&i.DurationMs,
&i.AssetMetadata,
&i.ContentHash,
); err != nil {
return nil, err
}
+4 -4
View File
@@ -133,7 +133,7 @@ SELECT
m.bot_id,
m.route_id,
m.sender_channel_identity_id,
m.sender_account_user_id AS sender_user_id,
COALESCE(ci.user_id, m.sender_account_user_id) AS sender_user_id,
m.channel_type AS platform,
m.source_message_id AS external_message_id,
m.source_reply_to_message_id,
@@ -211,7 +211,7 @@ SELECT
m.bot_id,
m.route_id,
m.sender_channel_identity_id,
m.sender_account_user_id AS sender_user_id,
COALESCE(ci.user_id, m.sender_account_user_id) AS sender_user_id,
m.channel_type AS platform,
m.source_message_id AS external_message_id,
m.source_reply_to_message_id,
@@ -296,7 +296,7 @@ SELECT
m.bot_id,
m.route_id,
m.sender_channel_identity_id,
m.sender_account_user_id AS sender_user_id,
COALESCE(ci.user_id, m.sender_account_user_id) AS sender_user_id,
m.channel_type AS platform,
m.source_message_id AS external_message_id,
m.source_reply_to_message_id,
@@ -379,7 +379,7 @@ SELECT
m.bot_id,
m.route_id,
m.sender_channel_identity_id,
m.sender_account_user_id AS sender_user_id,
COALESCE(ci.user_id, m.sender_account_user_id) AS sender_user_id,
m.channel_type AS platform,
m.source_message_id AS external_message_id,
m.source_reply_to_message_id,
+15 -31
View File
@@ -75,12 +75,12 @@ type BotHistoryMessage struct {
}
type BotHistoryMessageAsset struct {
ID pgtype.UUID `json:"id"`
MessageID pgtype.UUID `json:"message_id"`
AssetID pgtype.UUID `json:"asset_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
ID pgtype.UUID `json:"id"`
MessageID pgtype.UUID `json:"message_id"`
Role string `json:"role"`
Ordinal int32 `json:"ordinal"`
ContentHash string `json:"content_hash"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type BotMember struct {
@@ -150,9 +150,9 @@ type Container struct {
}
type ContainerVersion struct {
ID string `json:"id"`
ID pgtype.UUID `json:"id"`
ContainerID string `json:"container_id"`
SnapshotID string `json:"snapshot_id"`
SnapshotID pgtype.UUID `json:"snapshot_id"`
Version int32 `json:"version"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
@@ -186,23 +186,6 @@ type McpConnection struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type MediaAsset struct {
ID pgtype.UUID `json:"id"`
BotID pgtype.UUID `json:"bot_id"`
StorageProviderID pgtype.UUID `json:"storage_provider_id"`
ContentHash string `json:"content_hash"`
MediaType string `json:"media_type"`
Mime string `json:"mime"`
SizeBytes int64 `json:"size_bytes"`
StorageKey string `json:"storage_key"`
OriginalName pgtype.Text `json:"original_name"`
Width pgtype.Int4 `json:"width"`
Height pgtype.Int4 `json:"height"`
DurationMs pgtype.Int8 `json:"duration_ms"`
Metadata []byte `json:"metadata"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type Model struct {
ID pgtype.UUID `json:"id"`
ModelID string `json:"model_id"`
@@ -250,12 +233,13 @@ type SearchProvider struct {
}
type Snapshot struct {
ID string `json:"id"`
ContainerID string `json:"container_id"`
ParentSnapshotID pgtype.Text `json:"parent_snapshot_id"`
Snapshotter string `json:"snapshotter"`
Digest pgtype.Text `json:"digest"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
ID pgtype.UUID `json:"id"`
ContainerID string `json:"container_id"`
RuntimeSnapshotName string `json:"runtime_snapshot_name"`
ParentRuntimeSnapshotName pgtype.Text `json:"parent_runtime_snapshot_name"`
Snapshotter string `json:"snapshotter"`
Source string `json:"source"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type StorageProvider struct {
+169 -15
View File
@@ -11,8 +11,147 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const insertSnapshot = `-- name: InsertSnapshot :exec
INSERT INTO snapshots (id, container_id, parent_snapshot_id, snapshotter, digest)
const getSnapshotByContainerAndRuntimeName = `-- name: GetSnapshotByContainerAndRuntimeName :one
SELECT
id,
container_id,
runtime_snapshot_name,
parent_runtime_snapshot_name,
snapshotter,
source,
created_at
FROM snapshots
WHERE container_id = $1
AND runtime_snapshot_name = $2
LIMIT 1
`
type GetSnapshotByContainerAndRuntimeNameParams struct {
ContainerID string `json:"container_id"`
RuntimeSnapshotName string `json:"runtime_snapshot_name"`
}
func (q *Queries) GetSnapshotByContainerAndRuntimeName(ctx context.Context, arg GetSnapshotByContainerAndRuntimeNameParams) (Snapshot, error) {
row := q.db.QueryRow(ctx, getSnapshotByContainerAndRuntimeName, arg.ContainerID, arg.RuntimeSnapshotName)
var i Snapshot
err := row.Scan(
&i.ID,
&i.ContainerID,
&i.RuntimeSnapshotName,
&i.ParentRuntimeSnapshotName,
&i.Snapshotter,
&i.Source,
&i.CreatedAt,
)
return i, err
}
const listSnapshotsByContainerID = `-- name: ListSnapshotsByContainerID :many
SELECT
id,
container_id,
runtime_snapshot_name,
parent_runtime_snapshot_name,
snapshotter,
source,
created_at
FROM snapshots
WHERE container_id = $1
ORDER BY created_at DESC
`
func (q *Queries) ListSnapshotsByContainerID(ctx context.Context, containerID string) ([]Snapshot, error) {
rows, err := q.db.Query(ctx, listSnapshotsByContainerID, containerID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Snapshot
for rows.Next() {
var i Snapshot
if err := rows.Scan(
&i.ID,
&i.ContainerID,
&i.RuntimeSnapshotName,
&i.ParentRuntimeSnapshotName,
&i.Snapshotter,
&i.Source,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSnapshotsWithVersionByContainerID = `-- name: ListSnapshotsWithVersionByContainerID :many
SELECT
s.id,
s.container_id,
s.runtime_snapshot_name,
s.parent_runtime_snapshot_name,
s.snapshotter,
s.source,
s.created_at,
cv.version
FROM snapshots s
LEFT JOIN container_versions cv ON cv.snapshot_id = s.id
WHERE s.container_id = $1
ORDER BY s.created_at DESC
`
type ListSnapshotsWithVersionByContainerIDRow struct {
ID pgtype.UUID `json:"id"`
ContainerID string `json:"container_id"`
RuntimeSnapshotName string `json:"runtime_snapshot_name"`
ParentRuntimeSnapshotName pgtype.Text `json:"parent_runtime_snapshot_name"`
Snapshotter string `json:"snapshotter"`
Source string `json:"source"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
Version pgtype.Int4 `json:"version"`
}
func (q *Queries) ListSnapshotsWithVersionByContainerID(ctx context.Context, containerID string) ([]ListSnapshotsWithVersionByContainerIDRow, error) {
rows, err := q.db.Query(ctx, listSnapshotsWithVersionByContainerID, containerID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListSnapshotsWithVersionByContainerIDRow
for rows.Next() {
var i ListSnapshotsWithVersionByContainerIDRow
if err := rows.Scan(
&i.ID,
&i.ContainerID,
&i.RuntimeSnapshotName,
&i.ParentRuntimeSnapshotName,
&i.Snapshotter,
&i.Source,
&i.CreatedAt,
&i.Version,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const upsertSnapshot = `-- name: UpsertSnapshot :one
INSERT INTO snapshots (
container_id,
runtime_snapshot_name,
parent_runtime_snapshot_name,
snapshotter,
source
)
VALUES (
$1,
$2,
@@ -20,24 +159,39 @@ VALUES (
$4,
$5
)
ON CONFLICT (id) DO NOTHING
ON CONFLICT (container_id, runtime_snapshot_name) DO UPDATE
SET
parent_runtime_snapshot_name = EXCLUDED.parent_runtime_snapshot_name,
snapshotter = EXCLUDED.snapshotter,
source = EXCLUDED.source
RETURNING id, container_id, runtime_snapshot_name, parent_runtime_snapshot_name, snapshotter, source, created_at
`
type InsertSnapshotParams struct {
ID string `json:"id"`
ContainerID string `json:"container_id"`
ParentSnapshotID pgtype.Text `json:"parent_snapshot_id"`
Snapshotter string `json:"snapshotter"`
Digest pgtype.Text `json:"digest"`
type UpsertSnapshotParams struct {
ContainerID string `json:"container_id"`
RuntimeSnapshotName string `json:"runtime_snapshot_name"`
ParentRuntimeSnapshotName pgtype.Text `json:"parent_runtime_snapshot_name"`
Snapshotter string `json:"snapshotter"`
Source string `json:"source"`
}
func (q *Queries) InsertSnapshot(ctx context.Context, arg InsertSnapshotParams) error {
_, err := q.db.Exec(ctx, insertSnapshot,
arg.ID,
func (q *Queries) UpsertSnapshot(ctx context.Context, arg UpsertSnapshotParams) (Snapshot, error) {
row := q.db.QueryRow(ctx, upsertSnapshot,
arg.ContainerID,
arg.ParentSnapshotID,
arg.RuntimeSnapshotName,
arg.ParentRuntimeSnapshotName,
arg.Snapshotter,
arg.Digest,
arg.Source,
)
return err
var i Snapshot
err := row.Scan(
&i.ID,
&i.ContainerID,
&i.RuntimeSnapshotName,
&i.ParentRuntimeSnapshotName,
&i.Snapshotter,
&i.Source,
&i.CreatedAt,
)
return i, err
}
+44 -25
View File
@@ -7,49 +7,48 @@ package sqlc
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const getVersionSnapshotID = `-- name: GetVersionSnapshotID :one
SELECT snapshot_id FROM container_versions WHERE container_id = $1 AND version = $2
const getVersionSnapshotRuntimeName = `-- name: GetVersionSnapshotRuntimeName :one
SELECT s.runtime_snapshot_name
FROM container_versions cv
JOIN snapshots s ON s.id = cv.snapshot_id
WHERE cv.container_id = $1
AND cv.version = $2
`
type GetVersionSnapshotIDParams struct {
type GetVersionSnapshotRuntimeNameParams struct {
ContainerID string `json:"container_id"`
Version int32 `json:"version"`
}
func (q *Queries) GetVersionSnapshotID(ctx context.Context, arg GetVersionSnapshotIDParams) (string, error) {
row := q.db.QueryRow(ctx, getVersionSnapshotID, arg.ContainerID, arg.Version)
var snapshot_id string
err := row.Scan(&snapshot_id)
return snapshot_id, err
func (q *Queries) GetVersionSnapshotRuntimeName(ctx context.Context, arg GetVersionSnapshotRuntimeNameParams) (string, error) {
row := q.db.QueryRow(ctx, getVersionSnapshotRuntimeName, arg.ContainerID, arg.Version)
var runtime_snapshot_name string
err := row.Scan(&runtime_snapshot_name)
return runtime_snapshot_name, err
}
const insertVersion = `-- name: InsertVersion :one
INSERT INTO container_versions (id, container_id, snapshot_id, version)
INSERT INTO container_versions (container_id, snapshot_id, version)
VALUES (
$1,
$2,
$3,
$4
$3
)
RETURNING id, container_id, snapshot_id, version, created_at
`
type InsertVersionParams struct {
ID string `json:"id"`
ContainerID string `json:"container_id"`
SnapshotID string `json:"snapshot_id"`
Version int32 `json:"version"`
ContainerID string `json:"container_id"`
SnapshotID pgtype.UUID `json:"snapshot_id"`
Version int32 `json:"version"`
}
func (q *Queries) InsertVersion(ctx context.Context, arg InsertVersionParams) (ContainerVersion, error) {
row := q.db.QueryRow(ctx, insertVersion,
arg.ID,
arg.ContainerID,
arg.SnapshotID,
arg.Version,
)
row := q.db.QueryRow(ctx, insertVersion, arg.ContainerID, arg.SnapshotID, arg.Version)
var i ContainerVersion
err := row.Scan(
&i.ID,
@@ -62,24 +61,44 @@ func (q *Queries) InsertVersion(ctx context.Context, arg InsertVersionParams) (C
}
const listVersionsByContainerID = `-- name: ListVersionsByContainerID :many
SELECT id, container_id, snapshot_id, version, created_at FROM container_versions WHERE container_id = $1 ORDER BY version ASC
SELECT
cv.id,
cv.container_id,
cv.snapshot_id,
cv.version,
cv.created_at,
s.runtime_snapshot_name
FROM container_versions cv
JOIN snapshots s ON s.id = cv.snapshot_id
WHERE cv.container_id = $1
ORDER BY cv.version ASC
`
func (q *Queries) ListVersionsByContainerID(ctx context.Context, containerID string) ([]ContainerVersion, error) {
type ListVersionsByContainerIDRow struct {
ID pgtype.UUID `json:"id"`
ContainerID string `json:"container_id"`
SnapshotID pgtype.UUID `json:"snapshot_id"`
Version int32 `json:"version"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
RuntimeSnapshotName string `json:"runtime_snapshot_name"`
}
func (q *Queries) ListVersionsByContainerID(ctx context.Context, containerID string) ([]ListVersionsByContainerIDRow, error) {
rows, err := q.db.Query(ctx, listVersionsByContainerID, containerID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ContainerVersion
var items []ListVersionsByContainerIDRow
for rows.Next() {
var i ContainerVersion
var i ListVersionsByContainerIDRow
if err := rows.Scan(
&i.ID,
&i.ContainerID,
&i.SnapshotID,
&i.Version,
&i.CreatedAt,
&i.RuntimeSnapshotName,
); err != nil {
return nil, err
}