44406b72db
Moteva-style AI design workbench replica. Home prompt creates a project; the project page provides an infinite canvas with node drag, zoom/pan, a design chat panel, history replay, image asset upload, and project save/regenerate. - Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage; sky-valley/pi agent runtime adapter. - Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n, shadcn/ui components, auth (OTP/Turnstile/Google/WeChat). - Deploy: Docker Compose and k3s manifests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
3.0 KiB
Go
117 lines
3.0 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"img_infinite_canvas/internal/domain/design"
|
|
)
|
|
|
|
const projectListKeyPrefix = "design:projects:list"
|
|
|
|
type ProjectRepository struct {
|
|
next design.Repository
|
|
store Store
|
|
ttl time.Duration
|
|
}
|
|
|
|
func NewProjectRepository(next design.Repository, store Store, ttl time.Duration) *ProjectRepository {
|
|
return &ProjectRepository{
|
|
next: next,
|
|
store: store,
|
|
ttl: ttl,
|
|
}
|
|
}
|
|
|
|
func (r *ProjectRepository) List(ctx context.Context) ([]design.ProjectSummary, error) {
|
|
var projects []design.ProjectSummary
|
|
ok, err := r.store.Get(ctx, projectListKey(ctx), &projects)
|
|
if err == nil && ok {
|
|
return projects, nil
|
|
}
|
|
|
|
projects, err = r.next.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = r.store.Set(ctx, projectListKey(ctx), projects, r.ttl)
|
|
return projects, nil
|
|
}
|
|
|
|
func (r *ProjectRepository) ListPage(ctx context.Context, limit int64, offset int64) ([]design.ProjectSummary, error) {
|
|
return r.next.ListPage(ctx, limit, offset)
|
|
}
|
|
|
|
func (r *ProjectRepository) Get(ctx context.Context, id string) (design.Project, error) {
|
|
var project design.Project
|
|
ok, err := r.store.Get(ctx, projectKey(ctx, id), &project)
|
|
if err == nil && ok {
|
|
return project, nil
|
|
}
|
|
|
|
project, err = r.next.Get(ctx, id)
|
|
if err != nil {
|
|
return design.Project{}, err
|
|
}
|
|
_ = r.store.Set(ctx, projectKey(ctx, id), project, r.ttl)
|
|
return project, nil
|
|
}
|
|
|
|
func (r *ProjectRepository) Save(ctx context.Context, project design.Project) error {
|
|
if err := r.next.Save(ctx, project); err != nil {
|
|
return err
|
|
}
|
|
|
|
_ = r.store.Set(ctx, projectKey(ctx, project.ID), project, r.ttl)
|
|
_ = r.store.Delete(ctx, projectListKey(ctx))
|
|
return nil
|
|
}
|
|
|
|
func (r *ProjectRepository) SaveCanvas(ctx context.Context, project design.Project, baseVersion string) error {
|
|
next, ok := r.next.(design.CanvasRepository)
|
|
if !ok {
|
|
return r.Save(ctx, project)
|
|
}
|
|
if err := next.SaveCanvas(ctx, project, baseVersion); err != nil {
|
|
return err
|
|
}
|
|
_ = r.store.Delete(ctx, projectKey(ctx, project.ID))
|
|
_ = r.store.Delete(ctx, projectListKey(ctx))
|
|
return nil
|
|
}
|
|
|
|
func (r *ProjectRepository) Delete(ctx context.Context, id string) error {
|
|
if err := r.next.Delete(ctx, id); err != nil {
|
|
return err
|
|
}
|
|
|
|
_ = r.store.Delete(ctx, projectKey(ctx, id))
|
|
_ = r.store.Delete(ctx, projectListKey(ctx))
|
|
return nil
|
|
}
|
|
|
|
func (r *ProjectRepository) SaveMockupBlob(ctx context.Context, blob design.MockupBlobData) error {
|
|
next, ok := r.next.(design.MockupBlobRepository)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return next.SaveMockupBlob(ctx, blob)
|
|
}
|
|
|
|
func (r *ProjectRepository) GetMockupBlob(ctx context.Context, dataID string) (design.MockupBlobData, error) {
|
|
next, ok := r.next.(design.MockupBlobRepository)
|
|
if !ok {
|
|
return design.MockupBlobData{}, design.ErrNotFound
|
|
}
|
|
return next.GetMockupBlob(ctx, dataID)
|
|
}
|
|
|
|
func projectListKey(ctx context.Context) string {
|
|
return fmt.Sprintf("%s:%s", projectListKeyPrefix, design.UserIDFromContext(ctx))
|
|
}
|
|
|
|
func projectKey(ctx context.Context, id string) string {
|
|
return fmt.Sprintf("design:projects:%s:%s", design.UserIDFromContext(ctx), id)
|
|
}
|