diff --git a/db/migrations/000007_entities.up.sql b/db/migrations/000007_entities.up.sql index 1ab567e..a47455b 100644 --- a/db/migrations/000007_entities.up.sql +++ b/db/migrations/000007_entities.up.sql @@ -5,6 +5,8 @@ CREATE TABLE IF NOT EXISTS entities ( slug TEXT, description TEXT, status SMALLINT, + time_start INT, + time_end INT, is_deleted BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() @@ -16,6 +18,10 @@ ON entities USING GIN (name gin_trgm_ops); CREATE INDEX idx_entities_project_id ON entities(project_id); +CREATE INDEX idx_entities_time_range +ON entities USING GIST (int4range(time_start, time_end, '[]')) +WHERE is_deleted = false; + CREATE INDEX idx_entities_created_active ON entities(created_at DESC) WHERE is_deleted = false; diff --git a/db/migrations/000009_geometries.up.sql b/db/migrations/000009_geometries.up.sql index 6ad4a01..fdd549b 100644 --- a/db/migrations/000009_geometries.up.sql +++ b/db/migrations/000009_geometries.up.sql @@ -34,7 +34,7 @@ ON geometries USING GIST (bbox) WHERE is_deleted = false; CREATE INDEX idx_geom_time_range -ON geometries USING GIST (int4range(time_start, time_end)) +ON geometries USING GIST (int4range(time_start, time_end, '[]')) WHERE is_deleted = false; CREATE INDEX idx_entity_geometries_geometry diff --git a/db/query/entities.sql b/db/query/entities.sql index 4100e72..a818462 100644 --- a/db/query/entities.sql +++ b/db/query/entities.sql @@ -1,8 +1,8 @@ -- name: CreateEntity :one INSERT INTO entities ( - id, name, slug, description, project_id, status + id, name, slug, description, project_id, status, time_start, time_end ) VALUES ( - COALESCE(sqlc.narg('id')::uuid, uuidv7()), $1, $2, $3, $4, $5 + COALESCE(sqlc.narg('id')::uuid, uuidv7()), $1, $2, $3, $4, $5, $6, $7 ) RETURNING *; @@ -20,7 +20,9 @@ SET slug = COALESCE(sqlc.narg('slug'), slug), description = COALESCE(sqlc.narg('description'), description), project_id = COALESCE(sqlc.narg('project_id'), project_id), - status = COALESCE(sqlc.narg('status'), status) + status = COALESCE(sqlc.narg('status'), status), + time_start = COALESCE(sqlc.narg('time_start'), time_start), + time_end = COALESCE(sqlc.narg('time_end'), time_end) WHERE id = sqlc.arg('id') AND is_deleted = false RETURNING *; @@ -37,7 +39,11 @@ SELECT * FROM entities WHERE is_deleted = false AND (sqlc.narg('project_id')::uuid IS NULL OR project_id = sqlc.narg('project_id')::uuid) - AND name ILIKE '%' || sqlc.arg('name')::text || '%' + AND (sqlc.narg('name')::text IS NULL OR name ILIKE '%' || sqlc.narg('name')::text || '%') + AND ( + sqlc.narg('time_point')::int IS NULL OR + int4range(time_start, time_end, '[]') @> sqlc.narg('time_point')::int + ) AND (sqlc.narg('cursor_id')::uuid IS NULL OR id < sqlc.narg('cursor_id')::uuid) ORDER BY id DESC LIMIT sqlc.arg('limit_count'); diff --git a/db/query/geometries.sql b/db/query/geometries.sql index e931bd7..798e930 100644 --- a/db/query/geometries.sql +++ b/db/query/geometries.sql @@ -67,7 +67,7 @@ WHERE g.is_deleted = false ) AND ( sqlc.narg('time_point')::int IS NULL OR - (g.time_start <= sqlc.narg('time_point')::int AND g.time_end >= sqlc.narg('time_point')::int) + int4range(g.time_start, g.time_end, '[]') @> sqlc.narg('time_point')::int ) AND ( sqlc.narg('entity_id')::uuid IS NULL OR diff --git a/db/schema.sql b/db/schema.sql index 5946e96..46b951c 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -90,6 +90,8 @@ CREATE TABLE IF NOT EXISTS entities ( slug TEXT, description TEXT, status SMALLINT, + time_start INT, + time_end INT, is_deleted BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now() diff --git a/internal/dtos/request/snapshot.go b/internal/dtos/request/snapshot.go index 7371447..662261c 100644 --- a/internal/dtos/request/snapshot.go +++ b/internal/dtos/request/snapshot.go @@ -38,16 +38,17 @@ type FeatureProperties struct { } type EntitySnapshot struct { - 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"` + 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"` + Status *int `json:"status,omitempty" validate:"omitempty,oneof=0 1"` + TimeStart *float64 `json:"time_start,omitempty"` + TimeEnd *float64 `json:"time_end,omitempty"` + BaseUpdatedAt string `json:"base_updated_at,omitempty"` + BaseHash string `json:"base_hash,omitempty"` } type GeometrySnapshot struct { @@ -90,7 +91,5 @@ type EntityWikiLinkSnapshot struct { 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"` - - // Legacy / Compatibility - IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"` + IsDeleted *int `json:"is_deleted,omitempty" validate:"omitempty,oneof=0 1"` } diff --git a/internal/dtos/response/entity.go b/internal/dtos/response/entity.go index e477171..b302e14 100644 --- a/internal/dtos/response/entity.go +++ b/internal/dtos/response/entity.go @@ -9,6 +9,8 @@ type EntityResponse struct { Description string `json:"description,omitempty"` ProjectID string `json:"project_id"` Status *int16 `json:"status,omitempty"` + TimeStart *int32 `json:"time_start,omitempty"` + TimeEnd *int32 `json:"time_end,omitempty"` IsDeleted bool `json:"is_deleted,omitempty"` CreatedAt *time.Time `json:"created_at,omitempty"` UpdatedAt *time.Time `json:"updated_at,omitempty"` diff --git a/internal/gen/sqlc/entities.sql.go b/internal/gen/sqlc/entities.sql.go index c7e286e..cb56815 100644 --- a/internal/gen/sqlc/entities.sql.go +++ b/internal/gen/sqlc/entities.sql.go @@ -13,11 +13,11 @@ import ( const createEntity = `-- name: CreateEntity :one INSERT INTO entities ( - id, name, slug, description, project_id, status + id, name, slug, description, project_id, status, time_start, time_end ) VALUES ( - COALESCE($6::uuid, uuidv7()), $1, $2, $3, $4, $5 + COALESCE($8::uuid, uuidv7()), $1, $2, $3, $4, $5, $6, $7 ) -RETURNING id, project_id, name, slug, description, status, is_deleted, created_at, updated_at +RETURNING id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at ` type CreateEntityParams struct { @@ -26,6 +26,8 @@ type CreateEntityParams struct { Description pgtype.Text `json:"description"` ProjectID pgtype.UUID `json:"project_id"` Status pgtype.Int2 `json:"status"` + TimeStart pgtype.Int4 `json:"time_start"` + TimeEnd pgtype.Int4 `json:"time_end"` ID pgtype.UUID `json:"id"` } @@ -36,6 +38,8 @@ func (q *Queries) CreateEntity(ctx context.Context, arg CreateEntityParams) (Ent arg.Description, arg.ProjectID, arg.Status, + arg.TimeStart, + arg.TimeEnd, arg.ID, ) var i Entity @@ -46,6 +50,8 @@ func (q *Queries) CreateEntity(ctx context.Context, arg CreateEntityParams) (Ent &i.Slug, &i.Description, &i.Status, + &i.TimeStart, + &i.TimeEnd, &i.IsDeleted, &i.CreatedAt, &i.UpdatedAt, @@ -77,7 +83,7 @@ func (q *Queries) DeleteEntity(ctx context.Context, id pgtype.UUID) error { } const getEntitiesByIDs = `-- name: GetEntitiesByIDs :many -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 +SELECT id, project_id, name, slug, description, status, time_start, time_end, 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) { @@ -96,6 +102,8 @@ func (q *Queries) GetEntitiesByIDs(ctx context.Context, dollar_1 []pgtype.UUID) &i.Slug, &i.Description, &i.Status, + &i.TimeStart, + &i.TimeEnd, &i.IsDeleted, &i.CreatedAt, &i.UpdatedAt, @@ -111,7 +119,7 @@ func (q *Queries) GetEntitiesByIDs(ctx context.Context, dollar_1 []pgtype.UUID) } const getEntitiesByProjectId = `-- name: GetEntitiesByProjectId :many -SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at +SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at FROM entities WHERE project_id = $1 AND is_deleted = false ` @@ -132,6 +140,8 @@ func (q *Queries) GetEntitiesByProjectId(ctx context.Context, projectID pgtype.U &i.Slug, &i.Description, &i.Status, + &i.TimeStart, + &i.TimeEnd, &i.IsDeleted, &i.CreatedAt, &i.UpdatedAt, @@ -147,7 +157,7 @@ func (q *Queries) GetEntitiesByProjectId(ctx context.Context, projectID pgtype.U } const getEntityById = `-- name: GetEntityById :one -SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at +SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at FROM entities WHERE id = $1 AND is_deleted = false ` @@ -162,6 +172,8 @@ func (q *Queries) GetEntityById(ctx context.Context, id pgtype.UUID) (Entity, er &i.Slug, &i.Description, &i.Status, + &i.TimeStart, + &i.TimeEnd, &i.IsDeleted, &i.CreatedAt, &i.UpdatedAt, @@ -170,19 +182,24 @@ func (q *Queries) GetEntityById(ctx context.Context, id pgtype.UUID) (Entity, er } const searchEntities = `-- name: SearchEntities :many -SELECT id, project_id, name, slug, description, status, is_deleted, created_at, updated_at +SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at FROM entities WHERE is_deleted = false AND ($1::uuid IS NULL OR project_id = $1::uuid) - AND name ILIKE '%' || $2::text || '%' - AND ($3::uuid IS NULL OR id < $3::uuid) + AND ($2::text IS NULL OR name ILIKE '%' || $2::text || '%') + AND ( + $3::int IS NULL OR + int4range(time_start, time_end, '[]') @> $3::int + ) + AND ($4::uuid IS NULL OR id < $4::uuid) ORDER BY id DESC -LIMIT $4 +LIMIT $5 ` type SearchEntitiesParams struct { ProjectID pgtype.UUID `json:"project_id"` - Name string `json:"name"` + Name pgtype.Text `json:"name"` + TimePoint pgtype.Int4 `json:"time_point"` CursorID pgtype.UUID `json:"cursor_id"` LimitCount int32 `json:"limit_count"` } @@ -191,6 +208,7 @@ func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams) rows, err := q.db.Query(ctx, searchEntities, arg.ProjectID, arg.Name, + arg.TimePoint, arg.CursorID, arg.LimitCount, ) @@ -208,6 +226,8 @@ func (q *Queries) SearchEntities(ctx context.Context, arg SearchEntitiesParams) &i.Slug, &i.Description, &i.Status, + &i.TimeStart, + &i.TimeEnd, &i.IsDeleted, &i.CreatedAt, &i.UpdatedAt, @@ -229,9 +249,11 @@ SET 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 + status = COALESCE($5, status), + time_start = COALESCE($6, time_start), + time_end = COALESCE($7, time_end) +WHERE id = $8 AND is_deleted = false +RETURNING id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at ` type UpdateEntityParams struct { @@ -240,6 +262,8 @@ type UpdateEntityParams struct { Description pgtype.Text `json:"description"` ProjectID pgtype.UUID `json:"project_id"` Status pgtype.Int2 `json:"status"` + TimeStart pgtype.Int4 `json:"time_start"` + TimeEnd pgtype.Int4 `json:"time_end"` ID pgtype.UUID `json:"id"` } @@ -250,6 +274,8 @@ func (q *Queries) UpdateEntity(ctx context.Context, arg UpdateEntityParams) (Ent arg.Description, arg.ProjectID, arg.Status, + arg.TimeStart, + arg.TimeEnd, arg.ID, ) var i Entity @@ -260,6 +286,8 @@ func (q *Queries) UpdateEntity(ctx context.Context, arg UpdateEntityParams) (Ent &i.Slug, &i.Description, &i.Status, + &i.TimeStart, + &i.TimeEnd, &i.IsDeleted, &i.CreatedAt, &i.UpdatedAt, diff --git a/internal/gen/sqlc/geometries.sql.go b/internal/gen/sqlc/geometries.sql.go index f830761..0fd9f72 100644 --- a/internal/gen/sqlc/geometries.sql.go +++ b/internal/gen/sqlc/geometries.sql.go @@ -392,7 +392,7 @@ WHERE g.is_deleted = false ) AND ( $6::int IS NULL OR - (g.time_start <= $6::int AND g.time_end >= $6::int) + int4range(g.time_start, g.time_end, '[]') @> $6::int ) AND ( $7::uuid IS NULL OR diff --git a/internal/gen/sqlc/models.go b/internal/gen/sqlc/models.go index cb9e31a..d3c7a65 100644 --- a/internal/gen/sqlc/models.go +++ b/internal/gen/sqlc/models.go @@ -28,6 +28,8 @@ type Entity struct { Slug pgtype.Text `json:"slug"` Description pgtype.Text `json:"description"` Status pgtype.Int2 `json:"status"` + TimeStart pgtype.Int4 `json:"time_start"` + TimeEnd pgtype.Int4 `json:"time_end"` IsDeleted bool `json:"is_deleted"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` diff --git a/internal/models/entity.go b/internal/models/entity.go index 3c46023..ceaf13b 100644 --- a/internal/models/entity.go +++ b/internal/models/entity.go @@ -12,6 +12,8 @@ type EntityEntity struct { Description string `json:"description"` ProjectID string `json:"project_id"` Status *int16 `json:"status"` + TimeStart *int32 `json:"time_start"` + TimeEnd *int32 `json:"time_end"` IsDeleted bool `json:"is_deleted"` CreatedAt *time.Time `json:"created_at"` UpdatedAt *time.Time `json:"updated_at"` @@ -28,6 +30,8 @@ func (e *EntityEntity) ToResponse() *response.EntityResponse { Description: e.Description, ProjectID: e.ProjectID, Status: e.Status, + TimeStart: e.TimeStart, + TimeEnd: e.TimeEnd, IsDeleted: e.IsDeleted, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, diff --git a/internal/repositories/entityRepository.go b/internal/repositories/entityRepository.go index c4fa387..05f57d5 100644 --- a/internal/repositories/entityRepository.go +++ b/internal/repositories/entityRepository.go @@ -89,6 +89,8 @@ func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []strin Description: convert.TextToString(row.Description), ProjectID: convert.UUIDToString(row.ProjectID), Status: convert.Int2ToInt16Ptr(row.Status), + TimeStart: convert.Int4ToPtr(row.TimeStart), + TimeEnd: convert.Int4ToPtr(row.TimeEnd), IsDeleted: row.IsDeleted, CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), @@ -144,6 +146,8 @@ func (r *entityRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models Description: convert.TextToString(row.Description), ProjectID: convert.UUIDToString(row.ProjectID), Status: convert.Int2ToInt16Ptr(row.Status), + TimeStart: convert.Int4ToPtr(row.TimeStart), + TimeEnd: convert.Int4ToPtr(row.TimeEnd), IsDeleted: row.IsDeleted, CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), @@ -176,6 +180,8 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie Description: convert.TextToString(row.Description), ProjectID: convert.UUIDToString(row.ProjectID), Status: convert.Int2ToInt16Ptr(row.Status), + TimeStart: convert.Int4ToPtr(row.TimeStart), + TimeEnd: convert.Int4ToPtr(row.TimeEnd), IsDeleted: row.IsDeleted, CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), @@ -209,6 +215,8 @@ func (r *entityRepository) Create(ctx context.Context, params sqlc.CreateEntityP Description: convert.TextToString(row.Description), ProjectID: convert.UUIDToString(row.ProjectID), Status: convert.Int2ToInt16Ptr(row.Status), + TimeStart: convert.Int4ToPtr(row.TimeStart), + TimeEnd: convert.Int4ToPtr(row.TimeEnd), IsDeleted: row.IsDeleted, CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), @@ -229,6 +237,8 @@ func (r *entityRepository) Update(ctx context.Context, params sqlc.UpdateEntityP Description: convert.TextToString(row.Description), ProjectID: convert.UUIDToString(row.ProjectID), Status: convert.Int2ToInt16Ptr(row.Status), + TimeStart: convert.Int4ToPtr(row.TimeStart), + TimeEnd: convert.Int4ToPtr(row.TimeEnd), IsDeleted: row.IsDeleted, CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), @@ -270,6 +280,8 @@ func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype. Description: convert.TextToString(row.Description), ProjectID: convert.UUIDToString(row.ProjectID), Status: convert.Int2ToInt16Ptr(row.Status), + TimeStart: convert.Int4ToPtr(row.TimeStart), + TimeEnd: convert.Int4ToPtr(row.TimeEnd), IsDeleted: row.IsDeleted, CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), diff --git a/internal/services/entityService.go b/internal/services/entityService.go index 1f84a7e..acb5a70 100644 --- a/internal/services/entityService.go +++ b/internal/services/entityService.go @@ -55,8 +55,9 @@ func (s *entityService) SearchEntities(ctx context.Context, req *request.SearchE params.CursorID = cursor } } + if req.Name != "" { - params.Name = req.Name + params.Name = convert.PtrToText(&req.Name) } if req.ProjectID != nil { diff --git a/internal/services/submissionService.go b/internal/services/submissionService.go index 2a48e1a..ec3ac09 100644 --- a/internal/services/submissionService.go +++ b/internal/services/submissionService.go @@ -297,6 +297,8 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer Description: convert.StringToText(entity.Description), Slug: convert.PtrToText(entity.Slug), Status: convert.PtrToInt2(entity.Status), + TimeStart: convert.PtrFloat64ToInt4(entity.TimeStart), + TimeEnd: convert.PtrFloat64ToInt4(entity.TimeEnd), ID: entityUUID, }) @@ -314,6 +316,8 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer ProjectID: projectUUID, Slug: convert.PtrToText(entity.Slug), Status: convert.PtrToInt2(entity.Status), + TimeStart: convert.PtrFloat64ToInt4(entity.TimeStart), + TimeEnd: convert.PtrFloat64ToInt4(entity.TimeEnd), }) if err != nil {