Files
multica/server/pkg/db/generated/project_resource.sql.go
Bohan Jiang a4fac51cf5 fix(projects): add resource_count breadcrumb instead of inlining resources (#2118)
* fix(projects): add resource_count breadcrumb to project responses

Closes #2087

`multica project get` previously returned project metadata with no signal
that resources existed. Agents that fetched a project this way had no way
to discover its attached resources without already knowing about
`/api/projects/{id}/resources` or the on-disk `.multica/project/resources.json`.

Rather than inline the full resource list into the parent payload (which
conflates parent metadata with a child sub-collection and locks the
resource_ref shape into the project endpoint's contract), this adds a
scalar `resource_count` breadcrumb to ProjectResponse. The actual list
stays at the dedicated sub-collection endpoint.

Changes:
- GetProjectResourceCounts :many — new batched sqlc query
- ProjectResponse.ResourceCount populated in GetProject, ListProjects,
  SearchProjects, and the with-resources CreateProject echo
- multica project get prints a stderr hint pointing at
  multica project resource list <id> when count > 0; the JSON on stdout
  stays parseable
- Meta-skill (runtime_config.go) lists multica project get and
  multica project resource list in Available Commands so agents that
  read CLAUDE.md / AGENTS.md know about both paths

Co-authored-by: multica-agent <github@multica.ai>

* fix(projects): wire ResourceCount through Update + Create event payload

Review feedback on #2118.

- UpdateProject now reloads ResourceCount before responding/publishing.
  Previously a title- or status-only PUT served (and broadcast over WS)
  resource_count: 0 even when resources existed.
- The with-resources CreateProject path sets resp.ResourceCount before
  the project:created publish, so the WS event payload matches the HTTP
  echo. The hand-rolled response map collapses to an embedded
  ProjectResponse + resources array — one source of truth for the
  serialized shape.
- packages/core/types/project.ts: Project gains resource_count: number
  to keep the TS contract aligned with the server response.

Tests:
- TestProjectResourceCountBreadcrumb extends to assert UpdateProject
  preserves the breadcrumb.
- TestCreateProjectWithResourcesEchoesCount asserts the create echo
  carries resource_count matching the attached resources.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: multica-agent <github@multica.ai>
2026-05-06 14:09:35 +08:00

229 lines
6.0 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: project_resource.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countProjectResources = `-- name: CountProjectResources :one
SELECT count(*) FROM project_resource WHERE project_id = $1
`
func (q *Queries) CountProjectResources(ctx context.Context, projectID pgtype.UUID) (int64, error) {
row := q.db.QueryRow(ctx, countProjectResources, projectID)
var count int64
err := row.Scan(&count)
return count, err
}
const createProjectResource = `-- name: CreateProjectResource :one
INSERT INTO project_resource (
project_id, workspace_id, resource_type, resource_ref, label, position, created_by
) VALUES (
$1, $2, $3, $4, $5, $6, $7
) RETURNING id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by
`
type CreateProjectResourceParams struct {
ProjectID pgtype.UUID `json:"project_id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
ResourceType string `json:"resource_type"`
ResourceRef []byte `json:"resource_ref"`
Label pgtype.Text `json:"label"`
Position int32 `json:"position"`
CreatedBy pgtype.UUID `json:"created_by"`
}
func (q *Queries) CreateProjectResource(ctx context.Context, arg CreateProjectResourceParams) (ProjectResource, error) {
row := q.db.QueryRow(ctx, createProjectResource,
arg.ProjectID,
arg.WorkspaceID,
arg.ResourceType,
arg.ResourceRef,
arg.Label,
arg.Position,
arg.CreatedBy,
)
var i ProjectResource
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.WorkspaceID,
&i.ResourceType,
&i.ResourceRef,
&i.Label,
&i.Position,
&i.CreatedAt,
&i.CreatedBy,
)
return i, err
}
const deleteProjectResource = `-- name: DeleteProjectResource :exec
DELETE FROM project_resource WHERE id = $1
`
func (q *Queries) DeleteProjectResource(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteProjectResource, id)
return err
}
const getProjectResource = `-- name: GetProjectResource :one
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
WHERE id = $1
`
func (q *Queries) GetProjectResource(ctx context.Context, id pgtype.UUID) (ProjectResource, error) {
row := q.db.QueryRow(ctx, getProjectResource, id)
var i ProjectResource
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.WorkspaceID,
&i.ResourceType,
&i.ResourceRef,
&i.Label,
&i.Position,
&i.CreatedAt,
&i.CreatedBy,
)
return i, err
}
const getProjectResourceCounts = `-- name: GetProjectResourceCounts :many
SELECT project_id, count(*)::bigint AS resource_count
FROM project_resource
WHERE project_id = ANY($1::uuid[])
GROUP BY project_id
`
type GetProjectResourceCountsRow struct {
ProjectID pgtype.UUID `json:"project_id"`
ResourceCount int64 `json:"resource_count"`
}
func (q *Queries) GetProjectResourceCounts(ctx context.Context, projectIds []pgtype.UUID) ([]GetProjectResourceCountsRow, error) {
rows, err := q.db.Query(ctx, getProjectResourceCounts, projectIds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetProjectResourceCountsRow{}
for rows.Next() {
var i GetProjectResourceCountsRow
if err := rows.Scan(&i.ProjectID, &i.ResourceCount); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getProjectResourceInWorkspace = `-- name: GetProjectResourceInWorkspace :one
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
WHERE id = $1 AND workspace_id = $2
`
type GetProjectResourceInWorkspaceParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) GetProjectResourceInWorkspace(ctx context.Context, arg GetProjectResourceInWorkspaceParams) (ProjectResource, error) {
row := q.db.QueryRow(ctx, getProjectResourceInWorkspace, arg.ID, arg.WorkspaceID)
var i ProjectResource
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.WorkspaceID,
&i.ResourceType,
&i.ResourceRef,
&i.Label,
&i.Position,
&i.CreatedAt,
&i.CreatedBy,
)
return i, err
}
const listProjectResources = `-- name: ListProjectResources :many
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
WHERE project_id = $1
ORDER BY position ASC, created_at ASC
`
func (q *Queries) ListProjectResources(ctx context.Context, projectID pgtype.UUID) ([]ProjectResource, error) {
rows, err := q.db.Query(ctx, listProjectResources, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ProjectResource{}
for rows.Next() {
var i ProjectResource
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.WorkspaceID,
&i.ResourceType,
&i.ResourceRef,
&i.Label,
&i.Position,
&i.CreatedAt,
&i.CreatedBy,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectResourcesForProjects = `-- name: ListProjectResourcesForProjects :many
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
WHERE project_id = ANY($1::uuid[])
ORDER BY project_id, position ASC, created_at ASC
`
func (q *Queries) ListProjectResourcesForProjects(ctx context.Context, projectIds []pgtype.UUID) ([]ProjectResource, error) {
rows, err := q.db.Query(ctx, listProjectResourcesForProjects, projectIds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ProjectResource{}
for rows.Next() {
var i ProjectResource
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.WorkspaceID,
&i.ResourceType,
&i.ResourceRef,
&i.Label,
&i.Position,
&i.CreatedAt,
&i.CreatedBy,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}