UPDATE: Submission module
All checks were successful
Build and Release / release (push) Successful in 1m14s

This commit is contained in:
2026-05-04 09:55:17 +07:00
parent f3f2e09fd5
commit bcc2e192c1
48 changed files with 2918 additions and 359 deletions

View File

@@ -1,9 +1,7 @@
package request
import "encoding/json"
type CreateCommitDto struct {
SnapshotJson json.RawMessage `json:"snapshot_json" validate:"required"`
SnapshotJson *CommitSnapshot `json:"snapshot_json" validate:"required"`
EditSummary string `json:"edit_summary" validate:"required,max=500"`
}

View File

@@ -1,7 +1,8 @@
package request
type SearchEntityDto struct {
Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"`
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
Name string `json:"name" query:"name" validate:"omitempty,max=255"`
Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"`
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
Name string `json:"name" query:"name" validate:"omitempty,max=255"`
ProjectID *string `json:"project_id" query:"project_id" validate:"omitempty,uuid"`
}

View File

@@ -7,4 +7,5 @@ type SearchGeometryDto struct {
MaxLat *float64 `json:"max_lat" query:"max_lat" validate:"required,gte=-90,lte=90"`
TimePoint *int32 `json:"time" query:"time" validate:"omitempty,number"`
EntityID *string `json:"entity_id" query:"entity_id" validate:"omitempty,uuid"`
ProjectID *string `json:"project_id" query:"project_id" validate:"omitempty,uuid"`
}

View File

@@ -3,73 +3,63 @@ package request
import "encoding/json"
type CommitSnapshot struct {
SchemaVersion int `json:"schema_version" validate:"required"`
Section SectionRef `json:"section" validate:"required"`
EditorFeatureCollection *FeatureCollection `json:"editor_feature_collection,omitempty" validate:"omitempty"`
Entities []EntitySnapshot `json:"entities,omitempty" validate:"omitempty,dive"`
Geometries []GeometrySnapshot `json:"geometries,omitempty" validate:"omitempty,dive"`
LinkScopes []LinkScopeSnapshot `json:"link_scopes,omitempty" validate:"omitempty,dive"`
Wikis []WikiSnapshot `json:"wikis,omitempty" validate:"omitempty,dive"`
EntityWikis []EntityWikiLinkSnapshot `json:"entity_wikis,omitempty" validate:"omitempty,dive"`
}
type SectionRef struct {
ID string `json:"id" validate:"required"`
Title string `json:"title" validate:"required"`
EditorFeatureCollection *FeatureCollection `json:"editor_feature_collection,omitempty" validate:"omitempty"`
Entities []*EntitySnapshot `json:"entities,omitempty" validate:"omitempty,dive"`
Geometries []*GeometrySnapshot `json:"geometries,omitempty" validate:"omitempty,dive"`
Wikis []*WikiSnapshot `json:"wikis,omitempty" validate:"omitempty,dive"`
GeometryEntity []*GeometryEntitySnapshot `json:"geometry_entity,omitempty" validate:"omitempty,dive"`
EntityWiki []*EntityWikiLinkSnapshot `json:"entity_wiki,omitempty" validate:"omitempty,dive"`
EntityWikis []*EntityWikiLinkSnapshot `json:"entity_wikis,omitempty" validate:"omitempty,dive"`
}
type FeatureCollection struct {
Type string `json:"type" validate:"required,eq=FeatureCollection"`
Features []Feature `json:"features" validate:"required,dive"`
Type string `json:"type" validate:"required,eq=FeatureCollection"`
Features []*Feature `json:"features" validate:"required,dive"`
}
type Feature struct {
Type string `json:"type" validate:"required,eq=Feature"`
Properties FeatureProperties `json:"properties" validate:"required"`
Geometry json.RawMessage `json:"geometry" validate:"required"`
Type string `json:"type" validate:"required,eq=Feature"`
Properties *FeatureProperties `json:"properties" validate:"required"`
Geometry json.RawMessage `json:"geometry" validate:"required"`
}
type FeatureProperties struct {
ID any `json:"id" validate:"required"`
Type string `json:"type,omitempty"`
TimeStart *float64 `json:"time_start,omitempty"`
TimeEnd *float64 `json:"time_end,omitempty"`
Binding []string `json:"binding,omitempty"`
EntityID string `json:"entity_id,omitempty"`
EntityIDs []string `json:"entity_ids,omitempty"`
EntityName string `json:"entity_name,omitempty"`
EntityNames []string `json:"entity_names,omitempty"`
EntityTypeID string `json:"entity_type_id,omitempty"`
ID any `json:"id" validate:"required"`
Type string `json:"type,omitempty"`
GeometryPreset string `json:"geometry_preset,omitempty"`
TimeStart *float64 `json:"time_start,omitempty"`
TimeEnd *float64 `json:"time_end,omitempty"`
Binding []string `json:"binding,omitempty"`
EntityID string `json:"entity_id,omitempty" validate:"omitempty,uuidv7"`
EntityIDs []string `json:"entity_ids,omitempty" validate:"omitempty,dive,uuidv7"`
EntityName string `json:"entity_name,omitempty"`
EntityNames []string `json:"entity_names,omitempty"`
EntityTypeID string `json:"entity_type_id,omitempty" validate:"omitempty,uuidv7"`
}
type EntitySnapshot struct {
ID string `json:"id" validate:"required"`
Source string `json:"source,omitempty" validate:"omitempty,oneof=inline ref"`
Ref *Ref `json:"ref,omitempty" validate:"omitempty"`
Operation string `json:"operation,omitempty" validate:"omitempty,oneof=create update delete reference"`
Name string `json:"name,omitempty"`
Slug string `json:"slug,omitempty"`
Description string `json:"description,omitempty"`
TypeID string `json:"type_id,omitempty"`
Status *int `json:"status,omitempty" validate:"omitempty,oneof=0 1"`
IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"`
BaseUpdatedAt string `json:"base_updated_at,omitempty"`
BaseHash string `json:"base_hash,omitempty"`
ID string `json:"id" validate:"required,uuidv7"`
Source string `json:"source,omitempty" validate:"omitempty,oneof=inline ref"`
Operation string `json:"operation,omitempty" validate:"omitempty,oneof=create update delete reference"`
Name string `json:"name,omitempty"`
Slug *string `json:"slug,omitempty"`
Description string `json:"description,omitempty"`
TypeID string `json:"type_id,omitempty"`
Status *int `json:"status,omitempty" validate:"omitempty,oneof=0 1"`
BaseUpdatedAt string `json:"base_updated_at,omitempty"`
BaseHash string `json:"base_hash,omitempty"`
}
type GeometrySnapshot struct {
ID string `json:"id" validate:"required"`
ID string `json:"id" validate:"required,uuidv7"`
Source string `json:"source,omitempty" validate:"omitempty,oneof=inline ref"`
Ref *Ref `json:"ref,omitempty" validate:"omitempty"`
Operation string `json:"operation,omitempty" validate:"omitempty,oneof=create update delete reference"`
Type string `json:"type,omitempty"`
Type string `json:"type" validate:"required"`
DrawGeometry json.RawMessage `json:"draw_geometry,omitempty"`
Binding []string `json:"binding,omitempty"`
TimeStart *float64 `json:"time_start,omitempty"`
TimeEnd *float64 `json:"time_end,omitempty"`
BBox *BBox `json:"bbox,omitempty" validate:"omitempty"`
IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"`
BaseUpdatedAt string `json:"base_updated_at,omitempty"`
BaseHash string `json:"base_hash,omitempty"`
}
@@ -81,31 +71,26 @@ type BBox struct {
MaxLat float64 `json:"max_lat" validate:"required"`
}
type LinkScopeSnapshot struct {
GeometryID string `json:"geometry_id" validate:"required"`
Operation string `json:"operation" validate:"required,eq=reference"` // Theo doc chỉ có "reference"
EntityIDs []string `json:"entity_ids" validate:"required,min=1"` // Đã link thì phải có ít nhất 1 entity
BaseLinksHash string `json:"base_links_hash,omitempty"`
type GeometryEntitySnapshot struct {
GeometryID string `json:"geometry_id" validate:"required,uuidv7"`
EntityID string `json:"entity_id" validate:"required,uuidv7"`
BaseLinksHash string `json:"base_links_hash,omitempty"`
}
type WikiSnapshot struct {
ID string `json:"id" validate:"required"`
ID string `json:"id" validate:"required,uuidv7"`
Source string `json:"source,omitempty" validate:"omitempty,oneof=inline ref"`
Ref *Ref `json:"ref,omitempty" validate:"omitempty"`
Operation string `json:"operation,omitempty" validate:"omitempty,oneof=create update delete reference"`
Title string `json:"title" validate:"required"`
Doc json.RawMessage `json:"doc,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"`
}
type EntityWikiLinkSnapshot struct {
EntityID string `json:"entity_id" validate:"required"`
WikiID string `json:"wiki_id" validate:"required"`
EntityID string `json:"entity_id" validate:"required,uuidv7"`
WikiID string `json:"wiki_id" validate:"required,uuidv7"`
Operation string `json:"operation,omitempty" validate:"omitempty,oneof=reference delete"`
IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"`
}
type Ref struct {
ID string `json:"id" validate:"required"`
}
// Legacy / Compatibility
IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"`
}

View File

@@ -1,8 +1,9 @@
package request
type SearchWikiDto struct {
Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"`
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
Title string `json:"title" query:"title" validate:"omitempty,max=1000"`
EntityID string `json:"entity_id" query:"entity_id" validate:"omitempty,uuid"`
Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"`
ProjectID *string `json:"project_id" query:"project_id" validate:"omitempty,uuid"`
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
Title string `json:"title" query:"title" validate:"omitempty,max=1000"`
EntityID string `json:"entity_id" query:"entity_id" validate:"omitempty,uuid"`
}

View File

@@ -5,9 +5,11 @@ import "time"
type EntityResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug,omitempty"`
Description string `json:"description,omitempty"`
ThumbnailUrl string `json:"thumbnail_url,omitempty"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
ProjectID string `json:"project_id"`
Status *int16 `json:"status,omitempty"`
IsDeleted bool `json:"is_deleted,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@@ -20,6 +20,7 @@ type GeometryResponse struct {
TimeStart int32 `json:"time_start,omitempty"`
TimeEnd int32 `json:"time_end,omitempty"`
Bbox *Bbox `json:"bbox,omitempty"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`

View File

@@ -14,19 +14,24 @@ type MemberSimpleResponse struct {
AvatarUrl string `json:"avatar_url"`
}
type ProjectResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
LatestCommitID *string `json:"latest_commit_id,omitempty"`
ProjectStatus string `json:"project_status"`
LockedBy *string `json:"locked_by,omitempty"`
IsDeleted bool `json:"is_deleted"`
UserID string `json:"user_id"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
User *UserSimpleResponse `json:"user,omitempty"`
Commits []CommitSimpleResponse `json:"commits"`
SubmissionIds []string `json:"submission_ids"`
Members []MemberSimpleResponse `json:"members"`
type SubmissionSimpleResponse struct {
ID string `json:"id"`
Status string `json:"status"`
}
type ProjectResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
LatestCommitID *string `json:"latest_commit_id,omitempty"`
ProjectStatus string `json:"project_status"`
LockedBy *string `json:"locked_by,omitempty"`
IsDeleted bool `json:"is_deleted"`
UserID string `json:"user_id"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
User *UserSimpleResponse `json:"user,omitempty"`
Commits []CommitSimpleResponse `json:"commits"`
Submissions []SubmissionSimpleResponse `json:"submissions"`
Members []MemberSimpleResponse `json:"members"`
}

View File

@@ -1,12 +1,16 @@
package response
import "time"
import (
"encoding/json"
"time"
)
type WikiResponse struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
IsDeleted bool `json:"is_deleted,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
ID string `json:"id"`
Title string `json:"title,omitempty"`
Content json.RawMessage `json:"content,omitempty"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@@ -204,3 +204,31 @@ func (q *Queries) SearchCommits(ctx context.Context, arg SearchCommitsParams) ([
}
return items, nil
}
const updateCommitSnapshot = `-- name: UpdateCommitSnapshot :one
UPDATE commits
SET snapshot_json = $2
WHERE id = $1
RETURNING id, project_id, snapshot_json, snapshot_hash, user_id, edit_summary, is_deleted, created_at
`
type UpdateCommitSnapshotParams struct {
ID pgtype.UUID `json:"id"`
SnapshotJson json.RawMessage `json:"snapshot_json"`
}
func (q *Queries) UpdateCommitSnapshot(ctx context.Context, arg UpdateCommitSnapshotParams) (Commit, error) {
row := q.db.QueryRow(ctx, updateCommitSnapshot, arg.ID, arg.SnapshotJson)
var i Commit
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.SnapshotJson,
&i.SnapshotHash,
&i.UserID,
&i.EditSummary,
&i.IsDeleted,
&i.CreatedAt,
)
return i, err
}

View File

@@ -13,27 +13,39 @@ import (
const createEntity = `-- name: CreateEntity :one
INSERT INTO entities (
name, description, thumbnail_url
id, name, slug, description, project_id, status
) VALUES (
$1, $2, $3
COALESCE($6::uuid, uuidv7()), $1, $2, $3, $4, $5
)
RETURNING id, name, description, thumbnail_url, is_deleted, created_at, updated_at
RETURNING id, project_id, name, slug, description, status, is_deleted, created_at, updated_at
`
type CreateEntityParams struct {
Name string `json:"name"`
Description pgtype.Text `json:"description"`
ThumbnailUrl pgtype.Text `json:"thumbnail_url"`
Name string `json:"name"`
Slug pgtype.Text `json:"slug"`
Description pgtype.Text `json:"description"`
ProjectID pgtype.UUID `json:"project_id"`
Status pgtype.Int2 `json:"status"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) CreateEntity(ctx context.Context, arg CreateEntityParams) (Entity, error) {
row := q.db.QueryRow(ctx, createEntity, arg.Name, arg.Description, arg.ThumbnailUrl)
row := q.db.QueryRow(ctx, createEntity,
arg.Name,
arg.Slug,
arg.Description,
arg.ProjectID,
arg.Status,
arg.ID,
)
var i Entity
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Name,
&i.Slug,
&i.Description,
&i.ThumbnailUrl,
&i.Status,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
@@ -41,6 +53,17 @@ func (q *Queries) CreateEntity(ctx context.Context, arg CreateEntityParams) (Ent
return i, err
}
const deleteEntitiesByIDs = `-- name: DeleteEntitiesByIDs :exec
UPDATE entities
SET is_deleted = true
WHERE id = ANY($1::uuid[])
`
func (q *Queries) DeleteEntitiesByIDs(ctx context.Context, dollar_1 []pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteEntitiesByIDs, dollar_1)
return err
}
const deleteEntity = `-- name: DeleteEntity :exec
UPDATE entities
SET
@@ -54,7 +77,7 @@ func (q *Queries) DeleteEntity(ctx context.Context, id pgtype.UUID) error {
}
const getEntitiesByIDs = `-- name: GetEntitiesByIDs :many
SELECT id, name, description, thumbnail_url, is_deleted, created_at, updated_at FROM entities WHERE id = ANY($1::uuid[]) AND is_deleted = false
SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at FROM entities WHERE id = ANY($1::uuid[]) AND is_deleted = false
`
func (q *Queries) GetEntitiesByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Entity, error) {
@@ -68,9 +91,47 @@ func (q *Queries) GetEntitiesByIDs(ctx context.Context, dollar_1 []pgtype.UUID)
var i Entity
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Name,
&i.Slug,
&i.Description,
&i.ThumbnailUrl,
&i.Status,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getEntitiesByProjectId = `-- name: GetEntitiesByProjectId :many
SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at
FROM entities
WHERE project_id = $1 AND is_deleted = false
`
func (q *Queries) GetEntitiesByProjectId(ctx context.Context, projectID pgtype.UUID) ([]Entity, error) {
rows, err := q.db.Query(ctx, getEntitiesByProjectId, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Entity{}
for rows.Next() {
var i Entity
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Name,
&i.Slug,
&i.Description,
&i.Status,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
@@ -86,7 +147,7 @@ func (q *Queries) GetEntitiesByIDs(ctx context.Context, dollar_1 []pgtype.UUID)
}
const getEntityById = `-- name: GetEntityById :one
SELECT id, name, description, thumbnail_url, is_deleted, created_at, updated_at
SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at
FROM entities
WHERE id = $1 AND is_deleted = false
`
@@ -96,9 +157,11 @@ func (q *Queries) GetEntityById(ctx context.Context, id pgtype.UUID) (Entity, er
var i Entity
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Name,
&i.Slug,
&i.Description,
&i.ThumbnailUrl,
&i.Status,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
@@ -107,23 +170,30 @@ func (q *Queries) GetEntityById(ctx context.Context, id pgtype.UUID) (Entity, er
}
const searchEntities = `-- name: SearchEntities :many
SELECT id, name, description, thumbnail_url, is_deleted, created_at, updated_at
SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at
FROM entities
WHERE is_deleted = false
AND name ILIKE '%' || $1::text || '%'
AND ($2::uuid IS NULL OR id < $2::uuid)
AND ($1::uuid IS NULL OR project_id = $1::uuid)
AND name ILIKE '%' || $2::text || '%'
AND ($3::uuid IS NULL OR id < $3::uuid)
ORDER BY id DESC
LIMIT $3
LIMIT $4
`
type SearchEntitiesParams struct {
ProjectID pgtype.UUID `json:"project_id"`
Name string `json:"name"`
CursorID pgtype.UUID `json:"cursor_id"`
LimitCount int32 `json:"limit_count"`
}
func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams) ([]Entity, error) {
rows, err := q.db.Query(ctx, searchEntities, arg.Name, arg.CursorID, arg.LimitCount)
rows, err := q.db.Query(ctx, searchEntities,
arg.ProjectID,
arg.Name,
arg.CursorID,
arg.LimitCount,
)
if err != nil {
return nil, err
}
@@ -133,9 +203,11 @@ func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams)
var i Entity
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Name,
&i.Slug,
&i.Description,
&i.ThumbnailUrl,
&i.Status,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
@@ -154,32 +226,40 @@ const updateEntity = `-- name: UpdateEntity :one
UPDATE entities
SET
name = COALESCE($1, name),
description = COALESCE($2, description),
thumbnail_url = COALESCE($3, thumbnail_url)
WHERE id = $4 AND is_deleted = false
RETURNING id, name, description, thumbnail_url, is_deleted, created_at, updated_at
slug = COALESCE($2, slug),
description = COALESCE($3, description),
project_id = COALESCE($4, project_id),
status = COALESCE($5, status)
WHERE id = $6 AND is_deleted = false
RETURNING id, project_id, name, slug, description, status, is_deleted, created_at, updated_at
`
type UpdateEntityParams struct {
Name pgtype.Text `json:"name"`
Description pgtype.Text `json:"description"`
ThumbnailUrl pgtype.Text `json:"thumbnail_url"`
ID pgtype.UUID `json:"id"`
Name pgtype.Text `json:"name"`
Slug pgtype.Text `json:"slug"`
Description pgtype.Text `json:"description"`
ProjectID pgtype.UUID `json:"project_id"`
Status pgtype.Int2 `json:"status"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) UpdateEntity(ctx context.Context, arg UpdateEntityParams) (Entity, error) {
row := q.db.QueryRow(ctx, updateEntity,
arg.Name,
arg.Slug,
arg.Description,
arg.ThumbnailUrl,
arg.ProjectID,
arg.Status,
arg.ID,
)
var i Entity
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Name,
&i.Slug,
&i.Description,
&i.ThumbnailUrl,
&i.Status,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,

View File

@@ -38,30 +38,42 @@ func (q *Queries) BulkDeleteEntityGeometriesByEntityId(ctx context.Context, enti
return items, nil
}
const bulkDeleteEntityGeometriesByGeometryID = `-- name: BulkDeleteEntityGeometriesByGeometryID :exec
DELETE FROM entity_geometries
WHERE geometry_id = $1
`
func (q *Queries) BulkDeleteEntityGeometriesByGeometryID(ctx context.Context, geometryID pgtype.UUID) error {
_, err := q.db.Exec(ctx, bulkDeleteEntityGeometriesByGeometryID, geometryID)
return err
}
const createEntityGeometries = `-- name: CreateEntityGeometries :exec
INSERT INTO entity_geometries (
entity_id, geometry_id
entity_id, geometry_id, project_id
)
SELECT $1, unnest($2::uuid[])
SELECT $1, unnest($3::uuid[]), $2
ON CONFLICT DO NOTHING
`
type CreateEntityGeometriesParams struct {
EntityID pgtype.UUID `json:"entity_id"`
ProjectID pgtype.UUID `json:"project_id"`
GeometryIds []pgtype.UUID `json:"geometry_ids"`
}
func (q *Queries) CreateEntityGeometries(ctx context.Context, arg CreateEntityGeometriesParams) error {
_, err := q.db.Exec(ctx, createEntityGeometries, arg.EntityID, arg.GeometryIds)
_, err := q.db.Exec(ctx, createEntityGeometries, arg.EntityID, arg.ProjectID, arg.GeometryIds)
return err
}
const createGeometry = `-- name: CreateGeometry :one
INSERT INTO geometries (
geo_type, draw_geometry, binding, time_start, time_end, bbox
id, geo_type, draw_geometry, binding, time_start, time_end, bbox, project_id
) VALUES (
$1, $2, $3, $4, $5, ST_MakeEnvelope($6::float8, $7::float8, $8::float8, $9::float8, 4326)
COALESCE($7::uuid, uuidv7()), $1, $2, $3, $4, $5, ST_MakeEnvelope($8::float8, $9::float8, $10::float8, $11::float8, 4326), $6
)
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end,
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end, project_id,
ST_XMin(bbox)::float8 as min_lng, ST_YMin(bbox)::float8 as min_lat, ST_XMax(bbox)::float8 as max_lng, ST_YMax(bbox)::float8 as max_lat,
is_deleted, created_at, updated_at
`
@@ -72,6 +84,8 @@ type CreateGeometryParams struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
ID pgtype.UUID `json:"id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
@@ -85,6 +99,7 @@ type CreateGeometryRow struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
@@ -101,6 +116,8 @@ func (q *Queries) CreateGeometry(ctx context.Context, arg CreateGeometryParams)
arg.Binding,
arg.TimeStart,
arg.TimeEnd,
arg.ProjectID,
arg.ID,
arg.MinLng,
arg.MinLat,
arg.MaxLng,
@@ -114,6 +131,7 @@ func (q *Queries) CreateGeometry(ctx context.Context, arg CreateGeometryParams)
&i.Binding,
&i.TimeStart,
&i.TimeEnd,
&i.ProjectID,
&i.MinLng,
&i.MinLat,
&i.MaxLng,
@@ -125,6 +143,42 @@ func (q *Queries) CreateGeometry(ctx context.Context, arg CreateGeometryParams)
return i, err
}
const deleteEntityGeometriesByProjectID = `-- name: DeleteEntityGeometriesByProjectID :exec
DELETE FROM entity_geometries
WHERE project_id = $1
`
func (q *Queries) DeleteEntityGeometriesByProjectID(ctx context.Context, projectID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteEntityGeometriesByProjectID, projectID)
return err
}
const deleteEntityGeometry = `-- name: DeleteEntityGeometry :exec
DELETE FROM entity_geometries
WHERE entity_id = $1 AND geometry_id = $2
`
type DeleteEntityGeometryParams struct {
EntityID pgtype.UUID `json:"entity_id"`
GeometryID pgtype.UUID `json:"geometry_id"`
}
func (q *Queries) DeleteEntityGeometry(ctx context.Context, arg DeleteEntityGeometryParams) error {
_, err := q.db.Exec(ctx, deleteEntityGeometry, arg.EntityID, arg.GeometryID)
return err
}
const deleteGeometriesByIDs = `-- name: DeleteGeometriesByIDs :exec
UPDATE geometries
SET is_deleted = true
WHERE id = ANY($1::uuid[])
`
func (q *Queries) DeleteGeometriesByIDs(ctx context.Context, dollar_1 []pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteGeometriesByIDs, dollar_1)
return err
}
const deleteGeometry = `-- name: DeleteGeometry :exec
UPDATE geometries
SET
@@ -139,7 +193,7 @@ func (q *Queries) DeleteGeometry(ctx context.Context, id pgtype.UUID) error {
const getGeometriesByIDs = `-- name: GetGeometriesByIDs :many
SELECT
id, geo_type, draw_geometry, binding, time_start, time_end,
id, geo_type, draw_geometry, binding, time_start, time_end, project_id,
ST_XMin(bbox)::float8 as min_lng,
ST_YMin(bbox)::float8 as min_lat,
ST_XMax(bbox)::float8 as max_lng,
@@ -156,6 +210,7 @@ type GetGeometriesByIDsRow struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
@@ -181,6 +236,71 @@ func (q *Queries) GetGeometriesByIDs(ctx context.Context, dollar_1 []pgtype.UUID
&i.Binding,
&i.TimeStart,
&i.TimeEnd,
&i.ProjectID,
&i.MinLng,
&i.MinLat,
&i.MaxLng,
&i.MaxLat,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getGeometriesByProjectId = `-- name: GetGeometriesByProjectId :many
SELECT
id, geo_type, draw_geometry, binding, time_start, time_end, project_id,
ST_XMin(bbox)::float8 as min_lng,
ST_YMin(bbox)::float8 as min_lat,
ST_XMax(bbox)::float8 as max_lng,
ST_YMax(bbox)::float8 as max_lat,
is_deleted, created_at, updated_at
FROM geometries
WHERE project_id = $1 AND is_deleted = false
`
type GetGeometriesByProjectIdRow struct {
ID pgtype.UUID `json:"id"`
GeoType int16 `json:"geo_type"`
DrawGeometry json.RawMessage `json:"draw_geometry"`
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
MaxLat float64 `json:"max_lat"`
IsDeleted bool `json:"is_deleted"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
func (q *Queries) GetGeometriesByProjectId(ctx context.Context, projectID pgtype.UUID) ([]GetGeometriesByProjectIdRow, error) {
rows, err := q.db.Query(ctx, getGeometriesByProjectId, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetGeometriesByProjectIdRow{}
for rows.Next() {
var i GetGeometriesByProjectIdRow
if err := rows.Scan(
&i.ID,
&i.GeoType,
&i.DrawGeometry,
&i.Binding,
&i.TimeStart,
&i.TimeEnd,
&i.ProjectID,
&i.MinLng,
&i.MinLat,
&i.MaxLng,
@@ -200,7 +320,7 @@ func (q *Queries) GetGeometriesByIDs(ctx context.Context, dollar_1 []pgtype.UUID
}
const getGeometryById = `-- name: GetGeometryById :one
SELECT id, geo_type, draw_geometry, binding, time_start, time_end,
SELECT id, geo_type, draw_geometry, binding, time_start, time_end, project_id,
ST_XMin(bbox)::float8 as min_lng, ST_YMin(bbox)::float8 as min_lat, ST_XMax(bbox)::float8 as max_lng, ST_YMax(bbox)::float8 as max_lat,
is_deleted, created_at, updated_at
FROM geometries
@@ -214,6 +334,7 @@ type GetGeometryByIdRow struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
@@ -233,6 +354,7 @@ func (q *Queries) GetGeometryById(ctx context.Context, id pgtype.UUID) (GetGeome
&i.Binding,
&i.TimeStart,
&i.TimeEnd,
&i.ProjectID,
&i.MinLng,
&i.MinLat,
&i.MaxLng,
@@ -246,7 +368,7 @@ func (q *Queries) GetGeometryById(ctx context.Context, id pgtype.UUID) (GetGeome
const searchGeometries = `-- name: SearchGeometries :many
SELECT
g.id, g.geo_type, g.draw_geometry, g.binding, g.time_start, g.time_end,
g.id, g.geo_type, g.draw_geometry, g.binding, g.time_start, g.time_end, g.project_id,
ST_XMin(g.bbox)::float8 as min_lng,
ST_YMin(g.bbox)::float8 as min_lat,
ST_XMax(g.bbox)::float8 as max_lng,
@@ -254,36 +376,38 @@ SELECT
g.is_deleted, g.created_at, g.updated_at
FROM geometries g
WHERE g.is_deleted = false
AND ($1::uuid IS NULL OR g.project_id = $1::uuid)
AND (
$1::float8 IS NULL OR
$2::float8 IS NULL OR
$3::float8 IS NULL OR
$4::float8 IS NULL OR
$5::float8 IS NULL OR
g.bbox && ST_MakeEnvelope(
$1::float8,
$2::float8,
$3::float8,
$4::float8,
$5::float8,
4326
)
)
AND (
$5::int IS NULL OR
(g.time_start <= $5::int AND g.time_end >= $5::int)
$6::int IS NULL OR
(g.time_start <= $6::int AND g.time_end >= $6::int)
)
AND (
$6::uuid IS NULL OR
$7::uuid IS NULL OR
EXISTS (
SELECT 1
FROM entity_geometries eg
WHERE eg.geometry_id = g.id
AND eg.entity_id = $6::uuid
AND eg.entity_id = $7::uuid
)
)
ORDER BY g.id DESC
`
type SearchGeometriesParams struct {
ProjectID pgtype.UUID `json:"project_id"`
SearchMinLng pgtype.Float8 `json:"search_min_lng"`
SearchMinLat pgtype.Float8 `json:"search_min_lat"`
SearchMaxLng pgtype.Float8 `json:"search_max_lng"`
@@ -299,6 +423,7 @@ type SearchGeometriesRow struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
@@ -310,6 +435,7 @@ type SearchGeometriesRow struct {
func (q *Queries) SearchGeometries(ctx context.Context, arg SearchGeometriesParams) ([]SearchGeometriesRow, error) {
rows, err := q.db.Query(ctx, searchGeometries,
arg.ProjectID,
arg.SearchMinLng,
arg.SearchMinLat,
arg.SearchMaxLng,
@@ -331,6 +457,7 @@ func (q *Queries) SearchGeometries(ctx context.Context, arg SearchGeometriesPara
&i.Binding,
&i.TimeStart,
&i.TimeEnd,
&i.ProjectID,
&i.MinLng,
&i.MinLat,
&i.MaxLng,
@@ -357,14 +484,15 @@ SET
binding = COALESCE($3, binding),
time_start = COALESCE($4, time_start),
time_end = COALESCE($5, time_end),
project_id = COALESCE($6, project_id),
bbox = CASE
WHEN $6::boolean = true THEN
ST_MakeEnvelope($7::float8, $8::float8, $9::float8, $10::float8, 4326)
WHEN $7::boolean = true THEN
ST_MakeEnvelope($8::float8, $9::float8, $10::float8, $11::float8, 4326)
ELSE bbox
END,
updated_at = now()
WHERE id = $11 AND is_deleted = false
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end,
WHERE id = $12 AND is_deleted = false
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end, project_id,
ST_XMin(bbox)::float8 as min_lng, ST_YMin(bbox)::float8 as min_lat, ST_XMax(bbox)::float8 as max_lng, ST_YMax(bbox)::float8 as max_lat,
is_deleted, created_at, updated_at
`
@@ -375,6 +503,7 @@ type UpdateGeometryParams struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
UpdateBbox pgtype.Bool `json:"update_bbox"`
MinLng pgtype.Float8 `json:"min_lng"`
MinLat pgtype.Float8 `json:"min_lat"`
@@ -390,6 +519,7 @@ type UpdateGeometryRow struct {
Binding []byte `json:"binding"`
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
ProjectID pgtype.UUID `json:"project_id"`
MinLng float64 `json:"min_lng"`
MinLat float64 `json:"min_lat"`
MaxLng float64 `json:"max_lng"`
@@ -406,6 +536,7 @@ func (q *Queries) UpdateGeometry(ctx context.Context, arg UpdateGeometryParams)
arg.Binding,
arg.TimeStart,
arg.TimeEnd,
arg.ProjectID,
arg.UpdateBbox,
arg.MinLng,
arg.MinLat,
@@ -421,6 +552,7 @@ func (q *Queries) UpdateGeometry(ctx context.Context, arg UpdateGeometryParams)
&i.Binding,
&i.TimeStart,
&i.TimeEnd,
&i.ProjectID,
&i.MinLng,
&i.MinLat,
&i.MaxLng,

View File

@@ -22,23 +22,27 @@ type Commit struct {
}
type Entity struct {
ID pgtype.UUID `json:"id"`
Name string `json:"name"`
Description pgtype.Text `json:"description"`
ThumbnailUrl pgtype.Text `json:"thumbnail_url"`
IsDeleted bool `json:"is_deleted"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
ID pgtype.UUID `json:"id"`
ProjectID pgtype.UUID `json:"project_id"`
Name string `json:"name"`
Slug pgtype.Text `json:"slug"`
Description pgtype.Text `json:"description"`
Status pgtype.Int2 `json:"status"`
IsDeleted bool `json:"is_deleted"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type EntityGeometry struct {
EntityID pgtype.UUID `json:"entity_id"`
GeometryID pgtype.UUID `json:"geometry_id"`
ProjectID pgtype.UUID `json:"project_id"`
}
type EntityWiki struct {
EntityID pgtype.UUID `json:"entity_id"`
WikiID pgtype.UUID `json:"wiki_id"`
EntityID pgtype.UUID `json:"entity_id"`
WikiID pgtype.UUID `json:"wiki_id"`
ProjectID pgtype.UUID `json:"project_id"`
}
type Geometry struct {
@@ -49,6 +53,7 @@ type Geometry struct {
TimeStart pgtype.Int4 `json:"time_start"`
TimeEnd pgtype.Int4 `json:"time_end"`
Bbox interface{} `json:"bbox"`
ProjectID pgtype.UUID `json:"project_id"`
IsDeleted bool `json:"is_deleted"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
@@ -161,8 +166,9 @@ type VerificationMedia struct {
type Wiki struct {
ID pgtype.UUID `json:"id"`
ProjectID pgtype.UUID `json:"project_id"`
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
Content []byte `json:"content"`
IsDeleted bool `json:"is_deleted"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`

View File

@@ -136,7 +136,7 @@ SELECT
'avatar_url', up.avatar_url
)::json AS user,
'[]'::json AS commits,
'{}'::uuid[] AS submission_ids,
'[]'::json AS submissions,
'[]'::json AS members
FROM inserted_project p
JOIN users u ON p.user_id = u.id
@@ -163,7 +163,7 @@ type CreateProjectRow struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
User []byte `json:"user"`
Commits []byte `json:"commits"`
SubmissionIds []pgtype.UUID `json:"submission_ids"`
Submissions []byte `json:"submissions"`
Members []byte `json:"members"`
}
@@ -188,7 +188,7 @@ func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (C
&i.UpdatedAt,
&i.User,
&i.Commits,
&i.SubmissionIds,
&i.Submissions,
&i.Members,
)
return i, err
@@ -215,9 +215,9 @@ SELECT
'[]'
)::json AS commits,
COALESCE(
(SELECT array_agg(id) FROM submissions WHERE project_id = p.id),
'{}'
)::uuid[] AS submission_ids,
(SELECT json_agg(json_build_object('id', s.id, 'status', s.status)) FROM submissions s WHERE s.project_id = p.id),
'[]'
)::json AS submissions,
json_build_object(
'id', u.id,
'email', u.email,
@@ -254,7 +254,7 @@ type GetProjectByIdRow struct {
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Commits []byte `json:"commits"`
SubmissionIds []pgtype.UUID `json:"submission_ids"`
Submissions []byte `json:"submissions"`
User []byte `json:"user"`
Members []byte `json:"members"`
}
@@ -274,7 +274,7 @@ func (q *Queries) GetProjectById(ctx context.Context, id pgtype.UUID) (GetProjec
&i.CreatedAt,
&i.UpdatedAt,
&i.Commits,
&i.SubmissionIds,
&i.Submissions,
&i.User,
&i.Members,
)
@@ -289,7 +289,10 @@ SELECT
FROM commits c WHERE c.project_id = p.id AND c.is_deleted = false),
'[]'
)::json AS commits,
COALESCE((SELECT array_agg(id) FROM submissions WHERE project_id = p.id), '{}')::uuid[] AS submission_ids,
COALESCE(
(SELECT json_agg(json_build_object('id', s.id, 'status', s.status)) FROM submissions s WHERE s.project_id = p.id),
'[]'
)::json AS submissions,
json_build_object(
'id', u.id,
'email', u.email,
@@ -326,7 +329,7 @@ type GetProjectsByIDsRow struct {
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Commits []byte `json:"commits"`
SubmissionIds []pgtype.UUID `json:"submission_ids"`
Submissions []byte `json:"submissions"`
User []byte `json:"user"`
Members []byte `json:"members"`
}
@@ -352,7 +355,7 @@ func (q *Queries) GetProjectsByIDs(ctx context.Context, dollar_1 []pgtype.UUID)
&i.CreatedAt,
&i.UpdatedAt,
&i.Commits,
&i.SubmissionIds,
&i.Submissions,
&i.User,
&i.Members,
); err != nil {
@@ -375,9 +378,9 @@ SELECT
'[]'
)::json AS commits,
COALESCE(
(SELECT array_agg(id) FROM submissions WHERE project_id = p.id),
'{}'
)::uuid[] AS submission_ids,
(SELECT json_agg(json_build_object('id', s.id, 'status', s.status)) FROM submissions s WHERE s.project_id = p.id),
'[]'
)::json AS submissions,
json_build_object(
'id', u.id,
'email', u.email,
@@ -424,7 +427,7 @@ type GetProjectsByUserIdRow struct {
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Commits []byte `json:"commits"`
SubmissionIds []pgtype.UUID `json:"submission_ids"`
Submissions []byte `json:"submissions"`
User []byte `json:"user"`
Members []byte `json:"members"`
}
@@ -450,7 +453,7 @@ func (q *Queries) GetProjectsByUserId(ctx context.Context, arg GetProjectsByUser
&i.CreatedAt,
&i.UpdatedAt,
&i.Commits,
&i.SubmissionIds,
&i.Submissions,
&i.User,
&i.Members,
); err != nil {
@@ -488,9 +491,9 @@ SELECT
'[]'
)::json AS commits,
COALESCE(
(SELECT array_agg(id) FROM submissions WHERE project_id = p.id),
'{}'
)::uuid[] AS submission_ids,
(SELECT json_agg(json_build_object('id', s.id, 'status', s.status)) FROM submissions s WHERE s.project_id = p.id),
'[]'
)::json AS submissions,
json_build_object(
'id', u.id,
'email', u.email,
@@ -561,7 +564,7 @@ type SearchProjectsRow struct {
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Commits []byte `json:"commits"`
SubmissionIds []pgtype.UUID `json:"submission_ids"`
Submissions []byte `json:"submissions"`
User []byte `json:"user"`
Members []byte `json:"members"`
}
@@ -597,7 +600,7 @@ func (q *Queries) SearchProjects(ctx context.Context, arg SearchProjectsParams)
&i.CreatedAt,
&i.UpdatedAt,
&i.Commits,
&i.SubmissionIds,
&i.Submissions,
&i.User,
&i.Members,
); err != nil {
@@ -654,7 +657,10 @@ RETURNING
FROM commits c WHERE c.project_id = projects.id AND c.is_deleted = false),
'[]'
)::json AS commits,
COALESCE((SELECT array_agg(id) FROM submissions WHERE project_id = projects.id), '{}')::uuid[] AS submission_ids,
COALESCE(
(SELECT json_agg(json_build_object('id', s.id, 'status', s.status)) FROM submissions s WHERE s.project_id = projects.id),
'[]'
)::json AS submissions,
COALESCE(
(SELECT json_agg(json_build_object(
'user_id', pm.user_id, 'role', pm.role,
@@ -690,7 +696,7 @@ type UpdateProjectRow struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
User []byte `json:"user"`
Commits []byte `json:"commits"`
SubmissionIds []pgtype.UUID `json:"submission_ids"`
Submissions []byte `json:"submissions"`
Members []byte `json:"members"`
}
@@ -717,7 +723,7 @@ func (q *Queries) UpdateProject(ctx context.Context, arg UpdateProjectParams) (U
&i.UpdatedAt,
&i.User,
&i.Commits,
&i.SubmissionIds,
&i.Submissions,
&i.Members,
)
return i, err

View File

@@ -37,42 +37,62 @@ func (q *Queries) BulkDeleteEntityWikisByEntityId(ctx context.Context, entityID
return items, nil
}
const bulkDeleteEntityWikisByWikiID = `-- name: BulkDeleteEntityWikisByWikiID :exec
DELETE FROM entity_wikis
WHERE wiki_id = $1
`
func (q *Queries) BulkDeleteEntityWikisByWikiID(ctx context.Context, wikiID pgtype.UUID) error {
_, err := q.db.Exec(ctx, bulkDeleteEntityWikisByWikiID, wikiID)
return err
}
const createEntityWikis = `-- name: CreateEntityWikis :exec
INSERT INTO entity_wikis (
entity_id, wiki_id
entity_id, wiki_id, project_id
)
SELECT $1, unnest($2::uuid[])
SELECT $1, unnest($3::uuid[]), $2
ON CONFLICT DO NOTHING
`
type CreateEntityWikisParams struct {
EntityID pgtype.UUID `json:"entity_id"`
WikiIds []pgtype.UUID `json:"wiki_ids"`
EntityID pgtype.UUID `json:"entity_id"`
ProjectID pgtype.UUID `json:"project_id"`
WikiIds []pgtype.UUID `json:"wiki_ids"`
}
func (q *Queries) CreateEntityWikis(ctx context.Context, arg CreateEntityWikisParams) error {
_, err := q.db.Exec(ctx, createEntityWikis, arg.EntityID, arg.WikiIds)
_, err := q.db.Exec(ctx, createEntityWikis, arg.EntityID, arg.ProjectID, arg.WikiIds)
return err
}
const createWiki = `-- name: CreateWiki :one
INSERT INTO wikis (
title, content
id, title, content, project_id
) VALUES (
$1, $2
COALESCE($4::uuid, uuidv7()), $1, $2, $3
)
RETURNING id, title, content, is_deleted, created_at, updated_at
RETURNING id, project_id, title, content, is_deleted, created_at, updated_at
`
type CreateWikiParams struct {
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
Title pgtype.Text `json:"title"`
Content []byte `json:"content"`
ProjectID pgtype.UUID `json:"project_id"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) CreateWiki(ctx context.Context, arg CreateWikiParams) (Wiki, error) {
row := q.db.QueryRow(ctx, createWiki, arg.Title, arg.Content)
row := q.db.QueryRow(ctx, createWiki,
arg.Title,
arg.Content,
arg.ProjectID,
arg.ID,
)
var i Wiki
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Title,
&i.Content,
&i.IsDeleted,
@@ -82,6 +102,31 @@ func (q *Queries) CreateWiki(ctx context.Context, arg CreateWikiParams) (Wiki, e
return i, err
}
const deleteEntityWiki = `-- name: DeleteEntityWiki :exec
DELETE FROM entity_wikis
WHERE entity_id = $1 AND wiki_id = $2
`
type DeleteEntityWikiParams struct {
EntityID pgtype.UUID `json:"entity_id"`
WikiID pgtype.UUID `json:"wiki_id"`
}
func (q *Queries) DeleteEntityWiki(ctx context.Context, arg DeleteEntityWikiParams) error {
_, err := q.db.Exec(ctx, deleteEntityWiki, arg.EntityID, arg.WikiID)
return err
}
const deleteEntityWikisByProjectID = `-- name: DeleteEntityWikisByProjectID :exec
DELETE FROM entity_wikis
WHERE project_id = $1
`
func (q *Queries) DeleteEntityWikisByProjectID(ctx context.Context, projectID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteEntityWikisByProjectID, projectID)
return err
}
const deleteWiki = `-- name: DeleteWiki :exec
UPDATE wikis
SET
@@ -94,8 +139,19 @@ func (q *Queries) DeleteWiki(ctx context.Context, id pgtype.UUID) error {
return err
}
const deleteWikisByIDs = `-- name: DeleteWikisByIDs :exec
UPDATE wikis
SET is_deleted = true
WHERE id = ANY($1::uuid[])
`
func (q *Queries) DeleteWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteWikisByIDs, dollar_1)
return err
}
const getWikiById = `-- name: GetWikiById :one
SELECT id, title, content, is_deleted, created_at, updated_at
SELECT id, project_id, title, content, is_deleted, created_at, updated_at
FROM wikis
WHERE id = $1 AND is_deleted = false
`
@@ -105,6 +161,7 @@ func (q *Queries) GetWikiById(ctx context.Context, id pgtype.UUID) (Wiki, error)
var i Wiki
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Title,
&i.Content,
&i.IsDeleted,
@@ -115,7 +172,7 @@ func (q *Queries) GetWikiById(ctx context.Context, id pgtype.UUID) (Wiki, error)
}
const getWikisByIDs = `-- name: GetWikisByIDs :many
SELECT id, title, content, is_deleted, created_at, updated_at FROM wikis WHERE id = ANY($1::uuid[]) AND is_deleted = false
SELECT id, project_id, title, content, is_deleted, created_at, updated_at FROM wikis WHERE id = ANY($1::uuid[]) AND is_deleted = false
`
func (q *Queries) GetWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Wiki, error) {
@@ -129,6 +186,41 @@ func (q *Queries) GetWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]
var i Wiki
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Title,
&i.Content,
&i.IsDeleted,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getWikisByProjectId = `-- name: GetWikisByProjectId :many
SELECT id, project_id, title, content, is_deleted, created_at, updated_at
FROM wikis
WHERE project_id = $1 AND is_deleted = false
`
func (q *Queries) GetWikisByProjectId(ctx context.Context, projectID pgtype.UUID) ([]Wiki, error) {
rows, err := q.db.Query(ctx, getWikisByProjectId, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Wiki{}
for rows.Next() {
var i Wiki
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Title,
&i.Content,
&i.IsDeleted,
@@ -146,26 +238,28 @@ func (q *Queries) GetWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]
}
const searchWikis = `-- name: SearchWikis :many
SELECT w.id, w.title, w.content, w.is_deleted, w.created_at, w.updated_at
SELECT w.id, w.project_id, w.title, w.content, w.is_deleted, w.created_at, w.updated_at
FROM wikis w
WHERE w.is_deleted = false
AND w.title ILIKE '%' || $1::text || '%'
AND ($1::uuid IS NULL OR w.project_id = $1::uuid)
AND w.title ILIKE '%' || $2::text || '%'
AND (
$2::uuid IS NULL OR
$3::uuid IS NULL OR
EXISTS (
SELECT 1
FROM entity_wikis ew
WHERE ew.wiki_id = w.id
AND ew.entity_id = $2::uuid
AND ew.entity_id = $3::uuid
)
)
AND ($3::uuid IS NULL OR w.id < $3::uuid)
AND ($4::uuid IS NULL OR w.id < $4::uuid)
ORDER BY w.id DESC
LIMIT $4
LIMIT $5
`
type SearchWikisParams struct {
ProjectID pgtype.UUID `json:"project_id"`
Title string `json:"title"`
EntityID pgtype.UUID `json:"entity_id"`
CursorID pgtype.UUID `json:"cursor_id"`
@@ -174,6 +268,7 @@ type SearchWikisParams struct {
func (q *Queries) SearchWikis(ctx context.Context, arg SearchWikisParams) ([]Wiki, error) {
rows, err := q.db.Query(ctx, searchWikis,
arg.ProjectID,
arg.Title,
arg.EntityID,
arg.CursorID,
@@ -188,6 +283,7 @@ func (q *Queries) SearchWikis(ctx context.Context, arg SearchWikisParams) ([]Wik
var i Wiki
if err := rows.Scan(
&i.ID,
&i.ProjectID,
&i.Title,
&i.Content,
&i.IsDeleted,
@@ -208,22 +304,30 @@ const updateWiki = `-- name: UpdateWiki :one
UPDATE wikis
SET
title = COALESCE($1, title),
content = COALESCE($2, content)
WHERE id = $3 AND is_deleted = false
RETURNING id, title, content, is_deleted, created_at, updated_at
content = COALESCE($2, content),
project_id = COALESCE($3, project_id)
WHERE id = $4 AND is_deleted = false
RETURNING id, project_id, title, content, is_deleted, created_at, updated_at
`
type UpdateWikiParams struct {
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
ID pgtype.UUID `json:"id"`
Title pgtype.Text `json:"title"`
Content []byte `json:"content"`
ProjectID pgtype.UUID `json:"project_id"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) UpdateWiki(ctx context.Context, arg UpdateWikiParams) (Wiki, error) {
row := q.db.QueryRow(ctx, updateWiki, arg.Title, arg.Content, arg.ID)
row := q.db.QueryRow(ctx, updateWiki,
arg.Title,
arg.Content,
arg.ProjectID,
arg.ID,
)
var i Wiki
err := row.Scan(
&i.ID,
&i.ProjectID,
&i.Title,
&i.Content,
&i.IsDeleted,

View File

@@ -8,8 +8,10 @@ import (
type EntityEntity struct {
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Description string `json:"description"`
ThumbnailUrl string `json:"thumbnail_url"`
ProjectID string `json:"project_id"`
Status *int16 `json:"status"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
@@ -22,8 +24,10 @@ func (e *EntityEntity) ToResponse() *response.EntityResponse {
return &response.EntityResponse{
ID: e.ID,
Name: e.Name,
Slug: e.Slug,
Description: e.Description,
ThumbnailUrl: e.ThumbnailUrl,
ProjectID: e.ProjectID,
Status: e.Status,
IsDeleted: e.IsDeleted,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,

View File

@@ -15,6 +15,7 @@ type GeometryEntity struct {
TimeStart int32 `json:"time_start"`
TimeEnd int32 `json:"time_end"`
Bbox *response.Bbox `json:"bbox"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
@@ -32,6 +33,7 @@ func (g *GeometryEntity) ToResponse() *response.GeometryResponse {
TimeStart: g.TimeStart,
TimeEnd: g.TimeEnd,
Bbox: g.Bbox,
ProjectID: g.ProjectID,
IsDeleted: g.IsDeleted,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,

View File

@@ -19,6 +19,11 @@ type MemberSimple struct {
AvatarUrl string `json:"avatar_url"`
}
type SubmissionSimple struct {
ID string `json:"id"`
Status constants.StatusType `json:"status"`
}
type ProjectEntity struct {
ID string `json:"id"`
Title string `json:"title"`
@@ -32,7 +37,7 @@ type ProjectEntity struct {
UpdatedAt *time.Time `json:"updated_at"`
User *UserSimpleEntity `json:"user"`
Commits []CommitSimple `json:"commits"`
SubmissionIds []string `json:"submission_ids"`
Submissions []SubmissionSimple `json:"submissions"`
Members []MemberSimple `json:"members"`
}
@@ -60,6 +65,14 @@ func (p *ProjectEntity) ParseMembers(data []byte) error {
return json.Unmarshal(data, &p.Members)
}
func (p *ProjectEntity) ParseSubmissions(data []byte) error {
if len(data) == 0 || string(data) == "null" || string(data) == "[]" {
p.Submissions = []SubmissionSimple{}
return nil
}
return json.Unmarshal(data, &p.Submissions)
}
func (p *ProjectEntity) ToResponse() *response.ProjectResponse {
if p == nil {
return nil
@@ -77,6 +90,14 @@ func (p *ProjectEntity) ToResponse() *response.ProjectResponse {
})
}
submissions := make([]response.SubmissionSimpleResponse, 0, len(p.Submissions))
for _, s := range p.Submissions {
submissions = append(submissions, response.SubmissionSimpleResponse{
ID: s.ID,
Status: s.Status.String(),
})
}
members := make([]response.MemberSimpleResponse, 0, len(p.Members))
for _, m := range p.Members {
members = append(members, response.MemberSimpleResponse{
@@ -100,7 +121,7 @@ func (p *ProjectEntity) ToResponse() *response.ProjectResponse {
UpdatedAt: p.UpdatedAt,
User: userResponse,
Commits: commits,
SubmissionIds: p.SubmissionIds,
Submissions: submissions,
Members: members,
}
}

View File

@@ -1,17 +1,19 @@
package models
import (
"encoding/json"
"history-api/internal/dtos/response"
"time"
)
type WikiEntity struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
ID string `json:"id"`
Title string `json:"title"`
Content json.RawMessage `json:"content"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
func (w *WikiEntity) ToResponse() *response.WikiResponse {
@@ -22,6 +24,7 @@ func (w *WikiEntity) ToResponse() *response.WikiResponse {
ID: w.ID,
Title: w.Title,
Content: w.Content,
ProjectID: w.ProjectID,
IsDeleted: w.IsDeleted,
CreatedAt: w.CreatedAt,
UpdatedAt: w.UpdatedAt,

View File

@@ -20,6 +20,7 @@ type CommitRepository interface {
GetByID(ctx context.Context, id pgtype.UUID) (*models.CommitEntity, error)
GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.CommitEntity, error)
Search(ctx context.Context, params sqlc.SearchCommitsParams) ([]*models.CommitEntity, error)
UpdateSnapshot(ctx context.Context, id pgtype.UUID, snapshot json.RawMessage) (*models.CommitEntity, error)
WithTx(tx pgx.Tx) CommitRepository
}
@@ -228,3 +229,25 @@ func (r *commitRepository) Search(ctx context.Context, params sqlc.SearchCommits
return commits, nil
}
func (r *commitRepository) UpdateSnapshot(ctx context.Context, id pgtype.UUID, snapshot json.RawMessage) (*models.CommitEntity, error) {
row, err := r.q.UpdateCommitSnapshot(ctx, sqlc.UpdateCommitSnapshotParams{
ID: id,
SnapshotJson: snapshot,
})
if err != nil {
return nil, err
}
r.c.Del(ctx, fmt.Sprintf("commit:id:%s", convert.UUIDToString(id)))
return &models.CommitEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
SnapshotJson: row.SnapshotJson,
SnapshotHash: convert.TextToString(row.SnapshotHash),
UserID: convert.UUIDToString(row.UserID),
EditSummary: convert.TextToString(row.EditSummary),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
}, nil
}

View File

@@ -23,6 +23,8 @@ type EntityRepository interface {
Create(ctx context.Context, params sqlc.CreateEntityParams) (*models.EntityEntity, error)
Update(ctx context.Context, params sqlc.UpdateEntityParams) (*models.EntityEntity, error)
Delete(ctx context.Context, id pgtype.UUID) error
DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error
GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.EntityEntity, error)
WithTx(tx pgx.Tx) EntityRepository
}
@@ -83,8 +85,10 @@ func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []strin
item := models.EntityEntity{
ID: convert.UUIDToString(row.ID),
Name: row.Name,
Slug: convert.TextToString(row.Slug),
Description: convert.TextToString(row.Description),
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
ProjectID: convert.UUIDToString(row.ProjectID),
Status: convert.Int2ToInt16Ptr(row.Status),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -136,8 +140,10 @@ func (r *entityRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models
entity = models.EntityEntity{
ID: convert.UUIDToString(row.ID),
Name: row.Name,
Slug: convert.TextToString(row.Slug),
Description: convert.TextToString(row.Description),
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
ProjectID: convert.UUIDToString(row.ProjectID),
Status: convert.Int2ToInt16Ptr(row.Status),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -166,8 +172,10 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie
entity := &models.EntityEntity{
ID: convert.UUIDToString(row.ID),
Name: row.Name,
Slug: convert.TextToString(row.Slug),
Description: convert.TextToString(row.Description),
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
ProjectID: convert.UUIDToString(row.ProjectID),
Status: convert.Int2ToInt16Ptr(row.Status),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -180,6 +188,7 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie
if len(entityToCache) > 0 {
_ = r.c.MSet(ctx, entityToCache, constants.NormalCacheDuration)
}
if len(ids) > 0 {
_ = r.c.Set(ctx, queryKey, ids, constants.ListCacheDuration)
}
@@ -196,15 +205,15 @@ func (r *entityRepository) Create(ctx context.Context, params sqlc.CreateEntityP
entity := models.EntityEntity{
ID: convert.UUIDToString(row.ID),
Name: row.Name,
Slug: convert.TextToString(row.Slug),
Description: convert.TextToString(row.Description),
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
ProjectID: convert.UUIDToString(row.ProjectID),
Status: convert.Int2ToInt16Ptr(row.Status),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
go func() {
_ = r.c.DelByPattern(context.Background(), "entity:search*")
}()
return &entity, nil
}
@@ -216,8 +225,10 @@ func (r *entityRepository) Update(ctx context.Context, params sqlc.UpdateEntityP
entity := models.EntityEntity{
ID: convert.UUIDToString(row.ID),
Name: row.Name,
Slug: convert.TextToString(row.Slug),
Description: convert.TextToString(row.Description),
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
ProjectID: convert.UUIDToString(row.ProjectID),
Status: convert.Int2ToInt16Ptr(row.Status),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -234,3 +245,61 @@ func (r *entityRepository) Delete(ctx context.Context, id pgtype.UUID) error {
_ = r.c.Del(ctx, fmt.Sprintf("entity:id:%s", convert.UUIDToString(id)))
return nil
}
func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.EntityEntity, error) {
cacheKey := fmt.Sprintf("entity:project:%s", convert.UUIDToString(projectID))
var cachedIDs []string
if err := r.c.Get(ctx, cacheKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
return r.getByIDsWithFallback(ctx, cachedIDs)
}
rows, err := r.q.GetEntitiesByProjectId(ctx, projectID)
if err != nil {
return nil, err
}
var entities []*models.EntityEntity
var ids []string
entityToCache := make(map[string]any)
for _, row := range rows {
entity := &models.EntityEntity{
ID: convert.UUIDToString(row.ID),
Name: row.Name,
Slug: convert.TextToString(row.Slug),
Description: convert.TextToString(row.Description),
ProjectID: convert.UUIDToString(row.ProjectID),
Status: convert.Int2ToInt16Ptr(row.Status),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
ids = append(ids, entity.ID)
entities = append(entities, entity)
entityToCache[fmt.Sprintf("entity:id:%s", entity.ID)] = entity
}
if len(entityToCache) > 0 {
_ = r.c.MSet(ctx, entityToCache, constants.NormalCacheDuration)
}
if len(ids) > 0 {
_ = r.c.Set(ctx, cacheKey, ids, constants.ListCacheDuration)
}
return entities, nil
}
func (r *entityRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error {
err := r.q.DeleteEntitiesByIDs(ctx, ids)
if err != nil {
return err
}
if len(ids) > 0 {
keys := make([]string, len(ids))
for i, id := range ids {
keys[i] = fmt.Sprintf("entity:id:%s", convert.UUIDToString(id))
}
_ = r.c.Del(ctx, keys...)
}
return nil
}

View File

@@ -26,6 +26,11 @@ type GeometryRepository interface {
Delete(ctx context.Context, id pgtype.UUID) error
CreateEntityGeometries(ctx context.Context, params sqlc.CreateEntityGeometriesParams) error
BulkDeleteEntityGeometriesByEntityId(ctx context.Context, entityId pgtype.UUID) error
GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.GeometryEntity, error)
DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error
BulkDeleteEntityGeometriesByGeometryID(ctx context.Context, geometryID pgtype.UUID) error
DeleteEntityGeometry(ctx context.Context, entityID pgtype.UUID, geometryID pgtype.UUID) error
DeleteEntityGeometriesByProjectID(ctx context.Context, projectID pgtype.UUID) error
WithTx(tx pgx.Tx) GeometryRepository
}
@@ -96,6 +101,7 @@ func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []str
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
ProjectID: convert.UUIDToString(row.ProjectID),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -157,6 +163,7 @@ func (r *geometryRepository) GetByID(ctx context.Context, id pgtype.UUID) (*mode
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
ProjectID: convert.UUIDToString(row.ProjectID),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -195,6 +202,7 @@ func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeome
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
ProjectID: convert.UUIDToString(row.ProjectID),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -233,15 +241,12 @@ func (r *geometryRepository) Create(ctx context.Context, params sqlc.CreateGeome
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
ProjectID: convert.UUIDToString(row.ProjectID),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
go func() {
_ = r.c.DelByPattern(context.Background(), "geometry:search*")
}()
return &geometry, nil
}
@@ -263,6 +268,7 @@ func (r *geometryRepository) Update(ctx context.Context, params sqlc.UpdateGeome
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
ProjectID: convert.UUIDToString(row.ProjectID),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -281,26 +287,93 @@ func (r *geometryRepository) Delete(ctx context.Context, id pgtype.UUID) error {
}
func (r *geometryRepository) CreateEntityGeometries(ctx context.Context, params sqlc.CreateEntityGeometriesParams) error {
err := r.q.CreateEntityGeometries(ctx, params)
if err != nil {
return err
}
return err
return r.q.CreateEntityGeometries(ctx, params)
}
func (r *geometryRepository) DeleteEntityGeometriesByProjectID(ctx context.Context, projectID pgtype.UUID) error {
return r.q.DeleteEntityGeometriesByProjectID(ctx, projectID)
}
func (r *geometryRepository) BulkDeleteEntityGeometriesByEntityId(ctx context.Context, entityId pgtype.UUID) error {
geometryIDs, err := r.q.BulkDeleteEntityGeometriesByEntityId(ctx, entityId)
_, err := r.q.BulkDeleteEntityGeometriesByEntityId(ctx, entityId)
if err != nil {
return err
}
if len(geometryIDs) > 0 {
keys := make([]string, len(geometryIDs))
for i, id := range geometryIDs {
return nil
}
func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.GeometryEntity, error) {
cacheKey := fmt.Sprintf("geometry:project:%s", convert.UUIDToString(projectID))
var cachedIDs []string
if err := r.c.Get(ctx, cacheKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
return r.getByIDsWithFallback(ctx, cachedIDs)
}
rows, err := r.q.GetGeometriesByProjectId(ctx, projectID)
if err != nil {
return nil, err
}
var geometries []*models.GeometryEntity
var ids []string
geometryToCache := make(map[string]any)
for _, row := range rows {
geometry := &models.GeometryEntity{
ID: convert.UUIDToString(row.ID),
GeoType: constants.ParseGeoType(row.GeoType),
DrawGeometry: row.DrawGeometry,
Binding: row.Binding,
TimeStart: convert.Int4ToInt32(row.TimeStart),
TimeEnd: convert.Int4ToInt32(row.TimeEnd),
Bbox: &response.Bbox{
MinLng: row.MinLng,
MinLat: row.MinLat,
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
ProjectID: convert.UUIDToString(row.ProjectID),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
ids = append(ids, geometry.ID)
geometries = append(geometries, geometry)
geometryToCache[fmt.Sprintf("geometry:id:%s", geometry.ID)] = geometry
}
if len(geometryToCache) > 0 {
_ = r.c.MSet(ctx, geometryToCache, constants.NormalCacheDuration)
}
if len(ids) > 0 {
_ = r.c.Set(ctx, cacheKey, ids, constants.ListCacheDuration)
}
return geometries, nil
}
func (r *geometryRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error {
err := r.q.DeleteGeometriesByIDs(ctx, ids)
if err != nil {
return err
}
if len(ids) > 0 {
keys := make([]string, len(ids))
for i, id := range ids {
keys[i] = fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id))
}
go func() {
_ = r.c.Del(context.Background(), keys...)
}()
_ = r.c.Del(ctx, keys...)
}
return nil
}
func (r *geometryRepository) BulkDeleteEntityGeometriesByGeometryID(ctx context.Context, geometryID pgtype.UUID) error {
return r.q.BulkDeleteEntityGeometriesByGeometryID(ctx, geometryID)
}
func (r *geometryRepository) DeleteEntityGeometry(ctx context.Context, entityID pgtype.UUID, geometryID pgtype.UUID) error {
return r.q.DeleteEntityGeometry(ctx, sqlc.DeleteEntityGeometryParams{
EntityID: entityID,
GeometryID: geometryID,
})
}

View File

@@ -98,10 +98,10 @@ func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []stri
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = item.ParseUser(row.User)
_ = item.ParseCommits(row.Commits)
_ = item.ParseSubmissions(row.Submissions)
_ = item.ParseMembers(row.Members)
dbMap[item.ID] = &item
}
@@ -158,10 +158,10 @@ func (r *projectRepository) GetByID(ctx context.Context, id pgtype.UUID) (*model
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseSubmissions(row.Submissions)
_ = project.ParseMembers(row.Members)
_ = r.c.Set(ctx, cacheId, project, constants.NormalCacheDuration)
@@ -197,10 +197,10 @@ func (r *projectRepository) GetByUserID(ctx context.Context, params sqlc.GetProj
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseSubmissions(row.Submissions)
_ = project.ParseMembers(row.Members)
ids = append(ids, project.ID)
@@ -245,10 +245,10 @@ func (r *projectRepository) Search(ctx context.Context, params sqlc.SearchProjec
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseSubmissions(row.Submissions)
_ = project.ParseMembers(row.Members)
ids = append(ids, project.ID)
@@ -299,10 +299,10 @@ func (r *projectRepository) Create(ctx context.Context, params sqlc.CreateProjec
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseSubmissions(row.Submissions)
_ = project.ParseMembers(row.Members)
go func() {
@@ -330,10 +330,10 @@ func (r *projectRepository) Update(ctx context.Context, params sqlc.UpdateProjec
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseSubmissions(row.Submissions)
_ = project.ParseMembers(row.Members)
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", project.ID))

View File

@@ -25,6 +25,11 @@ type WikiRepository interface {
Delete(ctx context.Context, id pgtype.UUID) error
CreateEntityWikis(ctx context.Context, params sqlc.CreateEntityWikisParams) error
BulkDeleteEntityWikisByEntityId(ctx context.Context, entityId pgtype.UUID) error
GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.WikiEntity, error)
DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error
BulkDeleteEntityWikisByWikiID(ctx context.Context, wikiID pgtype.UUID) error
DeleteEntityWiki(ctx context.Context, entityID pgtype.UUID, wikiID pgtype.UUID) error
DeleteEntityWikisByProjectID(ctx context.Context, projectID pgtype.UUID) error
WithTx(tx pgx.Tx) WikiRepository
}
@@ -85,8 +90,9 @@ func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string)
item := models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: convert.TextToString(row.Content),
Content: json.RawMessage(row.Content),
IsDeleted: row.IsDeleted,
ProjectID: convert.UUIDToString(row.ProjectID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
@@ -137,7 +143,7 @@ func (r *wikiRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.W
wiki = models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: convert.TextToString(row.Content),
Content: json.RawMessage(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -166,7 +172,7 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara
wiki := &models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: convert.TextToString(row.Content),
Content: json.RawMessage(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -195,16 +201,12 @@ func (r *wikiRepository) Create(ctx context.Context, params sqlc.CreateWikiParam
wiki := models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: convert.TextToString(row.Content),
Content: json.RawMessage(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
go func() {
_ = r.c.DelByPattern(context.Background(), "wiki:search*")
}()
return &wiki, nil
}
@@ -216,7 +218,7 @@ func (r *wikiRepository) Update(ctx context.Context, params sqlc.UpdateWikiParam
wiki := models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: convert.TextToString(row.Content),
Content: json.RawMessage(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -235,26 +237,84 @@ func (r *wikiRepository) Delete(ctx context.Context, id pgtype.UUID) error {
}
func (r *wikiRepository) CreateEntityWikis(ctx context.Context, params sqlc.CreateEntityWikisParams) error {
err := r.q.CreateEntityWikis(ctx, params)
return r.q.CreateEntityWikis(ctx, params)
}
func (r *wikiRepository) DeleteEntityWikisByProjectID(ctx context.Context, projectID pgtype.UUID) error {
return r.q.DeleteEntityWikisByProjectID(ctx, projectID)
}
func (r *wikiRepository) BulkDeleteEntityWikisByEntityId(ctx context.Context, entityId pgtype.UUID) error {
_, err := r.q.BulkDeleteEntityWikisByEntityId(ctx, entityId)
if err != nil {
return err
}
return nil
}
func (r *wikiRepository) BulkDeleteEntityWikisByEntityId(ctx context.Context, entityId pgtype.UUID) error {
wikiIDs, err := r.q.BulkDeleteEntityWikisByEntityId(ctx, entityId)
func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.WikiEntity, error) {
cacheKey := fmt.Sprintf("wiki:project:%s", convert.UUIDToString(projectID))
var cachedIDs []string
if err := r.c.Get(ctx, cacheKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
return r.getByIDsWithFallback(ctx, cachedIDs)
}
rows, err := r.q.GetWikisByProjectId(ctx, projectID)
if err != nil {
return nil, err
}
var wikis []*models.WikiEntity
var ids []string
wikiToCache := make(map[string]any)
for _, row := range rows {
wiki := &models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
IsDeleted: row.IsDeleted,
ProjectID: convert.UUIDToString(row.ProjectID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
ids = append(ids, wiki.ID)
wikis = append(wikis, wiki)
wikiToCache[fmt.Sprintf("wiki:id:%s", wiki.ID)] = wiki
}
if len(wikiToCache) > 0 {
_ = r.c.MSet(ctx, wikiToCache, constants.NormalCacheDuration)
}
if len(ids) > 0 {
_ = r.c.Set(ctx, cacheKey, ids, constants.ListCacheDuration)
}
return wikis, nil
}
func (r *wikiRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error {
err := r.q.DeleteWikisByIDs(ctx, ids)
if err != nil {
return err
}
if len(wikiIDs) > 0 {
keys := make([]string, len(wikiIDs))
for i, id := range wikiIDs {
if len(ids) > 0 {
keys := make([]string, len(ids))
for i, id := range ids {
keys[i] = fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id))
}
go func() {
_ = r.c.Del(context.Background(), keys...)
}()
_ = r.c.Del(ctx, keys...)
}
return nil
}
func (r *wikiRepository) BulkDeleteEntityWikisByWikiID(ctx context.Context, wikiID pgtype.UUID) error {
return r.q.BulkDeleteEntityWikisByWikiID(ctx, wikiID)
}
func (r *wikiRepository) DeleteEntityWiki(ctx context.Context, entityID pgtype.UUID, wikiID pgtype.UUID) error {
return r.q.DeleteEntityWiki(ctx, sqlc.DeleteEntityWikiParams{
EntityID: entityID,
WikiID: wikiID,
})
}

View File

@@ -2,6 +2,7 @@ package services
import (
"context"
"encoding/json"
"history-api/internal/dtos/request"
"history-api/internal/dtos/response"
"history-api/internal/gen/sqlc"
@@ -63,6 +64,12 @@ func (s *commitService) checkWritePermission(ctx context.Context, userID string,
return fiber.NewError(fiber.StatusForbidden, "You do not have permission to write to this project")
}
for _, s := range project.Submissions {
if s.Status == constants.StatusTypePending {
return fiber.NewError(fiber.StatusConflict, "Cannot create commit while there is a pending submission")
}
}
return nil
}
@@ -90,9 +97,14 @@ func (s *commitService) CreateCommit(ctx context.Context, userID string, project
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
}
snapshotJSON, err := json.Marshal(dto.SnapshotJson)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid snapshot JSON")
}
commit, err := cRepoTx.Create(ctx, sqlc.CreateCommitParams{
ProjectID: projectUUID,
SnapshotJson: dto.SnapshotJson,
SnapshotJson: snapshotJSON,
UserID: userUUID,
EditSummary: convert.StringToText(dto.EditSummary),
})

View File

@@ -59,6 +59,13 @@ func (s *entityService) SearchEntities(ctx context.Context, req *request.SearchE
params.Name = req.Name
}
if req.ProjectID != nil {
projectID, err := convert.StringToUUID(*req.ProjectID)
if err == nil {
params.ProjectID = projectID
}
}
entities, err := s.entityRepo.Search(ctx, params)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to search entities")

View File

@@ -2,6 +2,7 @@ package services
import (
"context"
"encoding/json"
"fmt"
"history-api/internal/dtos/request"
"history-api/internal/dtos/response"
@@ -32,6 +33,9 @@ type submissionService struct {
projectRepo repositories.ProjectRepository
commitRepo repositories.CommitRepository
userRepo repositories.UserRepository
wikiRepo repositories.WikiRepository
geometryRepo repositories.GeometryRepository
entityRepo repositories.EntityRepository
db *pgxpool.Pool
c cache.Cache
}
@@ -41,6 +45,9 @@ func NewSubmissionService(
projectRepo repositories.ProjectRepository,
commitRepo repositories.CommitRepository,
userRepo repositories.UserRepository,
wikiRepo repositories.WikiRepository,
geometryRepo repositories.GeometryRepository,
entityRepo repositories.EntityRepository,
db *pgxpool.Pool,
c cache.Cache,
) SubmissionService {
@@ -49,6 +56,9 @@ func NewSubmissionService(
projectRepo: projectRepo,
commitRepo: commitRepo,
userRepo: userRepo,
wikiRepo: wikiRepo,
geometryRepo: geometryRepo,
entityRepo: entityRepo,
db: db,
c: c,
}
@@ -78,6 +88,17 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string,
return nil, fiber.NewError(fiber.StatusBadRequest, "Commit does not belong to project")
}
project, err := s.projectRepo.GetByID(ctx, projectUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusNotFound, "Project not found")
}
for _, sub := range project.Submissions {
if sub.Status == constants.StatusTypePending {
return nil, fiber.NewError(fiber.StatusConflict, "There is already a pending submission for this project")
}
}
arg := sqlc.CreateSubmissionParams{
ProjectID: projectUUID,
CommitID: commitUUID,
@@ -95,6 +116,18 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string,
}
func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewerID string, submissionID string, dto *request.UpdateSubmissionStatusDto) (*response.SubmissionResponse, *fiber.Error) {
tx, err := s.db.Begin(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
}
defer tx.Rollback(ctx)
submissionRepo := s.submissionRepo.WithTx(tx)
commitRepo := s.commitRepo.WithTx(tx)
entityRepo := s.entityRepo.WithTx(tx)
geometryRepo := s.geometryRepo.WithTx(tx)
wikiRepo := s.wikiRepo.WithTx(tx)
submissionUUID, err := convert.StringToUUID(submissionID)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid submission ID")
@@ -119,6 +152,404 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
return nil, fiber.NewError(fiber.StatusBadRequest, "Submission already processed")
}
commitUUID, err := convert.StringToUUID(submission.CommitID)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid commit ID")
}
commit, err := s.commitRepo.GetByID(ctx, commitUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusNotFound, "Commit not found")
}
if commit.ProjectID != submission.ProjectID {
return nil, fiber.NewError(fiber.StatusBadRequest, "Commit does not belong to project")
}
if status == constants.StatusTypeApproved {
var snapshotData request.CommitSnapshot
err = json.Unmarshal(commit.SnapshotJson, &snapshotData)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
}
projectUUID, err := convert.StringToUUID(commit.ProjectID)
if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project ID")
}
currentEntity, err := s.entityRepo.GetByProjectID(ctx, projectUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusNotFound, "Entity not found")
}
currentGeometry, err := s.geometryRepo.GetByProjectID(ctx, projectUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusNotFound, "Geometry not found")
}
currentWiki, err := s.wikiRepo.GetByProjectID(ctx, projectUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusNotFound, "Wiki not found")
}
persistItemIDs := make(map[string]struct{})
for _, item := range snapshotData.Entities {
persistItemIDs[item.ID] = struct{}{}
}
for _, item := range snapshotData.Geometries {
persistItemIDs[item.ID] = struct{}{}
}
for _, item := range snapshotData.Wikis {
persistItemIDs[item.ID] = struct{}{}
}
persistCurrentItemIDs := make(map[string]struct{})
for _, item := range currentEntity {
persistCurrentItemIDs[item.ID] = struct{}{}
}
for _, item := range currentGeometry {
persistCurrentItemIDs[item.ID] = struct{}{}
}
for _, item := range currentWiki {
persistCurrentItemIDs[item.ID] = struct{}{}
}
listDeleteEntities := make([]pgtype.UUID, 0)
for _, e := range currentEntity {
if _, ok := persistItemIDs[e.ID]; !ok {
itemUUID, err := convert.StringToUUID(e.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid entity ID")
}
listDeleteEntities = append(listDeleteEntities, itemUUID)
delete(persistCurrentItemIDs, e.ID)
}
}
listDeleteGeometries := make([]pgtype.UUID, 0)
for _, g := range currentGeometry {
if _, ok := persistItemIDs[g.ID]; !ok {
itemUUID, err := convert.StringToUUID(g.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID")
}
listDeleteGeometries = append(listDeleteGeometries, itemUUID)
delete(persistCurrentItemIDs, g.ID)
}
}
listDeleteWikis := make([]pgtype.UUID, 0)
for _, w := range currentWiki {
if _, ok := persistItemIDs[w.ID]; !ok {
itemUUID, err := convert.StringToUUID(w.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid wiki ID")
}
listDeleteWikis = append(listDeleteWikis, itemUUID)
delete(persistCurrentItemIDs, w.ID)
}
}
if len(listDeleteEntities) > 0 {
if err = entityRepo.DeleteByIDs(ctx, listDeleteEntities); err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete entities")
}
}
if len(listDeleteGeometries) > 0 {
if err = geometryRepo.DeleteByIDs(ctx, listDeleteGeometries); err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete geometries")
}
}
if len(listDeleteWikis) > 0 {
if err = wikiRepo.DeleteByIDs(ctx, listDeleteWikis); err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete wikis")
}
}
refEntityIDs := []string{}
for _, e := range snapshotData.Entities {
if e.Source == "ref" {
refEntityIDs = append(refEntityIDs, e.ID)
}
}
refEntities, _ := s.entityRepo.GetByIDs(ctx, refEntityIDs)
refEntityMap := make(map[string]bool)
for _, e := range refEntities {
refEntityMap[e.ID] = true
}
newEntities := make([]*request.EntitySnapshot, 0, len(snapshotData.Entities))
for i, entity := range snapshotData.Entities {
if entity.Operation == "delete" {
continue
}
entityUUID, err := convert.StringToUUID(entity.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid entity ID")
}
if _, ok := persistCurrentItemIDs[entity.ID]; ok {
_, err := entityRepo.Update(ctx, sqlc.UpdateEntityParams{
Name: convert.StringToText(entity.Name),
Description: convert.StringToText(entity.Description),
Slug: convert.PtrToText(entity.Slug),
Status: convert.PtrToInt2(entity.Status),
ID: entityUUID,
})
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update entity: "+entity.ID)
}
newEntities = append(newEntities, snapshotData.Entities[i])
} else if entity.Source == "inline" {
_, err := entityRepo.Create(ctx, sqlc.CreateEntityParams{
ID: entityUUID,
Name: entity.Name,
Description: convert.StringToText(entity.Description),
ProjectID: projectUUID,
Slug: convert.PtrToText(entity.Slug),
Status: convert.PtrToInt2(entity.Status),
})
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create entity: "+entity.ID)
}
newEntities = append(newEntities, snapshotData.Entities[i])
} else if entity.Source == "ref" {
if !refEntityMap[entity.ID] {
continue
}
newEntities = append(newEntities, snapshotData.Entities[i])
}
}
snapshotData.Entities = newEntities
refGeometryIDs := []string{}
for _, g := range snapshotData.Geometries {
if g.Source == "ref" {
refGeometryIDs = append(refGeometryIDs, g.ID)
}
}
refGeometries, _ := s.geometryRepo.GetByIDs(ctx, refGeometryIDs)
refGeometryMap := make(map[string]bool)
for _, g := range refGeometries {
refGeometryMap[g.ID] = true
}
newGeometries := make([]*request.GeometrySnapshot, 0, len(snapshotData.Geometries))
for i, geo := range snapshotData.Geometries {
if geo.Operation == "delete" {
continue
}
geometryUUID, err := convert.StringToUUID(geo.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID")
}
binding, _ := json.Marshal(geo.Binding)
if _, ok := persistCurrentItemIDs[geo.ID]; ok {
params := sqlc.UpdateGeometryParams{
ID: geometryUUID,
GeoType: pgtype.Int2{Int16: constants.ParseGeoTypeText(geo.Type).Int16(), Valid: true},
DrawGeometry: geo.DrawGeometry,
Binding: binding,
TimeStart: convert.PtrFloat64ToInt4(geo.TimeStart),
TimeEnd: convert.PtrFloat64ToInt4(geo.TimeEnd),
ProjectID: projectUUID,
}
if geo.BBox != nil {
params.UpdateBbox = pgtype.Bool{Bool: true, Valid: true}
params.MinLng = pgtype.Float8{Float64: geo.BBox.MinLng, Valid: true}
params.MinLat = pgtype.Float8{Float64: geo.BBox.MinLat, Valid: true}
params.MaxLng = pgtype.Float8{Float64: geo.BBox.MaxLng, Valid: true}
params.MaxLat = pgtype.Float8{Float64: geo.BBox.MaxLat, Valid: true}
}
_, err := geometryRepo.Update(ctx, params)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update geometry: "+geo.ID)
}
newGeometries = append(newGeometries, snapshotData.Geometries[i])
} else if geo.Source == "inline" {
params := sqlc.CreateGeometryParams{
ID: geometryUUID,
GeoType: constants.ParseGeoTypeText(geo.Type).Int16(),
DrawGeometry: geo.DrawGeometry,
Binding: binding,
TimeStart: convert.PtrFloat64ToInt4(geo.TimeStart),
TimeEnd: convert.PtrFloat64ToInt4(geo.TimeEnd),
ProjectID: projectUUID,
}
if geo.BBox != nil {
params.MinLng = geo.BBox.MinLng
params.MinLat = geo.BBox.MinLat
params.MaxLng = geo.BBox.MaxLng
params.MaxLat = geo.BBox.MaxLat
}
_, err := geometryRepo.Create(ctx, params)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create geometry: "+geo.ID)
}
newGeometries = append(newGeometries, snapshotData.Geometries[i])
} else if geo.Source == "ref" {
if !refGeometryMap[geo.ID] {
continue
}
newGeometries = append(newGeometries, snapshotData.Geometries[i])
}
}
snapshotData.Geometries = newGeometries
refWikiIDs := []string{}
for _, w := range snapshotData.Wikis {
if w.Source == "ref" {
refWikiIDs = append(refWikiIDs, w.ID)
}
}
refWikis, _ := s.wikiRepo.GetByIDs(ctx, refWikiIDs)
refWikiMap := make(map[string]bool)
for _, w := range refWikis {
refWikiMap[w.ID] = true
}
newWikis := make([]*request.WikiSnapshot, 0, len(snapshotData.Wikis))
for i, wiki := range snapshotData.Wikis {
if wiki.Operation == "delete" {
continue
}
wikiUUID, err := convert.StringToUUID(wiki.ID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid wiki ID")
}
if _, ok := persistCurrentItemIDs[wiki.ID]; ok {
_, err := wikiRepo.Update(ctx, sqlc.UpdateWikiParams{
ID: wikiUUID,
Title: convert.StringToText(wiki.Title),
Content: wiki.Doc,
ProjectID: projectUUID,
})
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update wiki: "+wiki.ID)
}
newWikis = append(newWikis, snapshotData.Wikis[i])
} else if wiki.Source == "inline" {
_, err := wikiRepo.Create(ctx, sqlc.CreateWikiParams{
ID: wikiUUID,
Title: convert.StringToText(wiki.Title),
Content: wiki.Doc,
ProjectID: projectUUID,
})
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki: "+wiki.ID)
}
newWikis = append(newWikis, snapshotData.Wikis[i])
} else if wiki.Source == "ref" {
if !refWikiMap[wiki.ID] {
continue
}
newWikis = append(newWikis, snapshotData.Wikis[i])
}
}
snapshotData.Wikis = newWikis
err = geometryRepo.DeleteEntityGeometriesByProjectID(ctx, projectUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete geometry entity: "+err.Error())
}
err = wikiRepo.DeleteEntityWikisByProjectID(ctx, projectUUID)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete wiki entity: "+err.Error())
}
validEntities := make(map[string]bool)
for _, e := range snapshotData.Entities {
validEntities[e.ID] = true
}
validGeometries := make(map[string]bool)
for _, g := range snapshotData.Geometries {
validGeometries[g.ID] = true
}
validWikis := make(map[string]bool)
for _, w := range snapshotData.Wikis {
validWikis[w.ID] = true
}
if len(snapshotData.GeometryEntity) > 0 {
geomLinks := make(map[string][]pgtype.UUID)
for _, link := range snapshotData.GeometryEntity {
if !validEntities[link.EntityID] || !validGeometries[link.GeometryID] {
continue
}
gID, _ := convert.StringToUUID(link.GeometryID)
geomLinks[link.EntityID] = append(geomLinks[link.EntityID], gID)
}
for eIDStr, gIDs := range geomLinks {
eID, _ := convert.StringToUUID(eIDStr)
err = geometryRepo.CreateEntityGeometries(ctx, sqlc.CreateEntityGeometriesParams{
EntityID: eID,
GeometryIds: gIDs,
ProjectID: projectUUID,
})
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create geometry entity: "+err.Error())
}
}
}
if len(snapshotData.EntityWiki) > 0 {
wikiLinks := make(map[string][]pgtype.UUID)
for _, link := range snapshotData.EntityWiki {
if link.Operation == "delete" || (link.IsDeleted != nil && *link.IsDeleted == 1) {
continue
}
if !validEntities[link.EntityID] || !validWikis[link.WikiID] {
continue
}
wID, _ := convert.StringToUUID(link.WikiID)
wikiLinks[link.EntityID] = append(wikiLinks[link.EntityID], wID)
}
for eIDStr, wIDs := range wikiLinks {
eID, _ := convert.StringToUUID(eIDStr)
err = wikiRepo.CreateEntityWikis(ctx, sqlc.CreateEntityWikisParams{
EntityID: eID,
WikiIds: wIDs,
ProjectID: projectUUID,
})
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki entity: "+err.Error())
}
}
}
newSnapshot, err := json.Marshal(snapshotData)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal snapshot")
}
_, err = commitRepo.UpdateSnapshot(ctx, commitUUID, newSnapshot)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update snapshot: "+err.Error())
}
}
arg := sqlc.UpdateSubmissionParams{
ID: submissionUUID,
Status: pgtype.Int2{Int16: status.Int16(), Valid: true},
@@ -126,11 +557,25 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
ReviewNote: convert.StringToText(dto.ReviewNote),
}
updatedSubmission, err := s.submissionRepo.Update(ctx, arg)
updatedSubmission, err := submissionRepo.Update(ctx, arg)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update submission status")
}
err = tx.Commit(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction")
}
if status == constants.StatusTypeApproved {
go func() {
bgCtx := context.Background()
_ = s.c.DelByPattern(bgCtx, "entity:search*")
_ = s.c.DelByPattern(bgCtx, "geometry:search*")
_ = s.c.DelByPattern(bgCtx, "wiki:search*")
}()
}
_ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", submission.ProjectID))
return updatedSubmission.ToResponse(), nil

View File

@@ -58,6 +58,7 @@ func (s *wikiService) SearchWikis(ctx context.Context, req *request.SearchWikiDt
if req.Title != "" {
params.Title = req.Title
}
if req.EntityID != "" {
entityId, err := convert.StringToUUID(req.EntityID)
if err == nil {
@@ -65,6 +66,13 @@ func (s *wikiService) SearchWikis(ctx context.Context, req *request.SearchWikiDt
}
}
if req.ProjectID != nil {
projectID, err := convert.StringToUUID(*req.ProjectID)
if err == nil {
params.ProjectID = projectID
}
}
wikis, err := s.wikiRepo.Search(ctx, params)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to search wikis")