This commit is contained in:
@@ -1,2 +1 @@
|
|||||||
DROP TABLE IF EXISTS entity_types;
|
|
||||||
DROP TABLE IF EXISTS entities;
|
DROP TABLE IF EXISTS entities;
|
||||||
@@ -1,48 +1,20 @@
|
|||||||
CREATE TABLE IF NOT EXISTS entity_types (
|
|
||||||
id SMALLSERIAL PRIMARY KEY,
|
|
||||||
name TEXT UNIQUE NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS entities (
|
CREATE TABLE IF NOT EXISTS entities (
|
||||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||||
type_id SMALLINT REFERENCES entity_types(id),
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
slug TEXT UNIQUE,
|
|
||||||
description TEXT,
|
description TEXT,
|
||||||
thumbnail_url TEXT,
|
thumbnail_url TEXT,
|
||||||
status SMALLINT DEFAULT 1, -- 1 draft, 2 published
|
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||||
reviewed_by UUID REFERENCES users(id),
|
|
||||||
reviewed_at TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT now()
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX uniq_entities_slug_active
|
|
||||||
ON entities(slug)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
CREATE INDEX idx_entities_type
|
|
||||||
ON entities(type_id)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE INDEX idx_entities_status_created
|
|
||||||
ON entities(status, created_at DESC)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE INDEX idx_entities_type_status
|
|
||||||
ON entities(type_id, status)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE INDEX idx_entities_reviewed_by
|
|
||||||
ON entities(reviewed_by)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
CREATE INDEX idx_entities_name_search
|
CREATE INDEX idx_entities_name_search
|
||||||
ON entities USING gin (name gin_trgm_ops);
|
ON entities USING GIN (name gin_trgm_ops);
|
||||||
|
|
||||||
|
CREATE INDEX idx_entities_created_active
|
||||||
|
ON entities(created_at DESC)
|
||||||
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
CREATE TRIGGER trigger_entities_updated_at
|
CREATE TRIGGER trigger_entities_updated_at
|
||||||
BEFORE UPDATE ON entities
|
BEFORE UPDATE ON entities
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
DROP TABLE IF EXISTS wiki_pages;
|
DROP TABLE IF EXISTS wikis;
|
||||||
DROP TABLE IF EXISTS wiki_versions;
|
DROP TABLE IF EXISTS entity_wikis;
|
||||||
@@ -1,17 +1,30 @@
|
|||||||
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS wikis (
|
CREATE TABLE IF NOT EXISTS wikis (
|
||||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||||
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
|
||||||
user_id UUID REFERENCES users(id),
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
note TEXT,
|
|
||||||
content TEXT,
|
content TEXT,
|
||||||
|
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT now()
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_wiki_entity
|
|
||||||
ON wikis(entity_id)
|
CREATE TABLE IF NOT EXISTS entity_wikis (
|
||||||
|
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
||||||
|
wiki_id UUID REFERENCES wikis(id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (entity_id, wiki_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_entity_wikis_wiki_id
|
||||||
|
ON entity_wikis(wiki_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_wikis_created_active
|
||||||
|
ON wikis(created_at DESC)
|
||||||
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
|
CREATE INDEX idx_wikis_title_search
|
||||||
|
ON wikis USING GIN (title gin_trgm_ops)
|
||||||
WHERE is_deleted = false;
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
CREATE TRIGGER trigger_wikis_updated_at
|
CREATE TRIGGER trigger_wikis_updated_at
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
DROP TABLE IF EXISTS geometries;
|
DROP TABLE IF EXISTS geometries;
|
||||||
DROP TABLE IF EXISTS geo_versions;
|
|
||||||
DROP TABLE IF EXISTS entity_geometries;
|
DROP TABLE IF EXISTS entity_geometries;
|
||||||
@@ -1,27 +1,22 @@
|
|||||||
CREATE EXTENSION IF NOT EXISTS btree_gist;
|
CREATE EXTENSION IF NOT EXISTS btree_gist;
|
||||||
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS geometries (
|
CREATE TABLE IF NOT EXISTS geometries (
|
||||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||||
geom GEOMETRY, -- point / polygon / line
|
geo_type VARCHAR(50) NOT NULL DEFAULT 'id'
|
||||||
|
draw_geometry JSONB NOT NULL,
|
||||||
|
binding JSONB,
|
||||||
time_start INT,
|
time_start INT,
|
||||||
time_end INT,
|
time_end INT,
|
||||||
|
bbox GEOMETRY,
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||||
bbox GEOMETRY, -- optional
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now(),
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT now()
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS geo_versions (
|
ALTER TABLE geometries DROP CONSTRAINT IF EXISTS check_geo_type;
|
||||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
ALTER TABLE geometries ADD CONSTRAINT check_geo_type
|
||||||
geo_id UUID REFERENCES geometries(id) ON DELETE CASCADE,
|
CHECK (geo_type IN ('id', 'name', 'icon', 'variant', 'description'));
|
||||||
created_user UUID REFERENCES users(id),
|
|
||||||
geom GEOMETRY,
|
|
||||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
note TEXT,
|
|
||||||
reviewed_by UUID REFERENCES users(id),
|
|
||||||
reviewed_at TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS entity_geometries (
|
CREATE TABLE IF NOT EXISTS entity_geometries (
|
||||||
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
||||||
@@ -29,34 +24,27 @@ CREATE TABLE IF NOT EXISTS entity_geometries (
|
|||||||
PRIMARY KEY (entity_id, geometry_id)
|
PRIMARY KEY (entity_id, geometry_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_geom_spatial_active
|
CREATE INDEX idx_geom_draw_geometry
|
||||||
ON geometries USING GIST (geom)
|
ON geometries USING GIN (draw_geometry);
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
CREATE INDEX idx_geom_bbox
|
CREATE INDEX idx_geom_bbox
|
||||||
ON geometries USING GIST (bbox)
|
ON geometries USING GIST (bbox)
|
||||||
WHERE is_deleted = false;
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
CREATE INDEX idx_geom_time_range
|
CREATE INDEX idx_geom_time_range
|
||||||
ON geometries
|
ON geometries USING GIST (int4range(time_start, time_end))
|
||||||
USING GIST (int4range(time_start, time_end))
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
CREATE INDEX idx_geo_versions_geo_id
|
|
||||||
ON geo_versions(geo_id)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
CREATE INDEX idx_geo_versions_reviewed_by
|
|
||||||
ON geo_versions(reviewed_by)
|
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
|
||||||
CREATE INDEX idx_geo_versions_created_at
|
|
||||||
ON geo_versions(created_at DESC)
|
|
||||||
WHERE is_deleted = false;
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
CREATE INDEX idx_entity_geometries_geometry
|
CREATE INDEX idx_entity_geometries_geometry
|
||||||
ON entity_geometries(geometry_id);
|
ON entity_geometries(geometry_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_geom_binding
|
||||||
|
ON geometries USING GIN (binding);
|
||||||
|
|
||||||
|
CREATE INDEX idx_geom_updated_at
|
||||||
|
ON geometries (updated_at DESC)
|
||||||
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
CREATE TRIGGER trigger_geometries_updated_at
|
CREATE TRIGGER trigger_geometries_updated_at
|
||||||
BEFORE UPDATE ON geometries
|
BEFORE UPDATE ON geometries
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
|
|||||||
40
db/query/entities.sql
Normal file
40
db/query/entities.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
-- name: CreateEntity :one
|
||||||
|
INSERT INTO entities (
|
||||||
|
name, description, thumbnail_url
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: GetEntityById :one
|
||||||
|
SELECT *
|
||||||
|
FROM entities
|
||||||
|
WHERE id = $1 AND is_deleted = false;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: UpdateEntity :one
|
||||||
|
UPDATE entities
|
||||||
|
SET
|
||||||
|
name = COALESCE(sqlc.narg('name'), name),
|
||||||
|
description = COALESCE(sqlc.narg('description'), description),
|
||||||
|
thumbnail_url = COALESCE(sqlc.narg('thumbnail_url'), thumbnail_url)
|
||||||
|
WHERE id = sqlc.arg('id') AND is_deleted = false
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: DeleteEntity :exec
|
||||||
|
UPDATE entities
|
||||||
|
SET
|
||||||
|
is_deleted = true
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: SearchEntities :many
|
||||||
|
SELECT *
|
||||||
|
FROM entities
|
||||||
|
WHERE is_deleted = false
|
||||||
|
AND name ILIKE '%' || sqlc.arg('name')::text || '%'
|
||||||
|
AND (sqlc.narg('cursor_id')::uuid IS NULL OR id < sqlc.narg('cursor_id')::uuid)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT sqlc.arg('limit_count');
|
||||||
90
db/query/geometries.sql
Normal file
90
db/query/geometries.sql
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
-- name: CreateGeometry :one
|
||||||
|
INSERT INTO geometries (
|
||||||
|
geo_type, draw_geometry, binding, time_start, time_end, bbox
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, ST_MakeEnvelope(sqlc.arg('min_lng')::float8, sqlc.arg('min_lat')::float8, sqlc.arg('max_lng')::float8, sqlc.arg('max_lat')::float8, 4326)
|
||||||
|
)
|
||||||
|
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end,
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- name: GetGeometryById :one
|
||||||
|
SELECT id, geo_type, draw_geometry, binding, time_start, time_end,
|
||||||
|
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 id = $1 AND is_deleted = false;
|
||||||
|
|
||||||
|
-- name: UpdateGeometry :one
|
||||||
|
UPDATE geometries
|
||||||
|
SET
|
||||||
|
geo_type = COALESCE(sqlc.narg('geo_type'), geo_type),
|
||||||
|
draw_geometry = COALESCE(sqlc.narg('draw_geometry'), draw_geometry),
|
||||||
|
binding = COALESCE(sqlc.narg('binding'), binding),
|
||||||
|
time_start = COALESCE(sqlc.narg('time_start'), time_start),
|
||||||
|
time_end = COALESCE(sqlc.narg('time_end'), time_end),
|
||||||
|
bbox = CASE
|
||||||
|
WHEN sqlc.narg('update_bbox')::boolean = true THEN
|
||||||
|
ST_MakeEnvelope(sqlc.narg('min_lng')::float8, sqlc.narg('min_lat')::float8, sqlc.narg('max_lng')::float8, sqlc.narg('max_lat')::float8, 4326)
|
||||||
|
ELSE bbox
|
||||||
|
END,
|
||||||
|
updated_at = now()
|
||||||
|
WHERE id = sqlc.arg('id') AND is_deleted = false
|
||||||
|
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end,
|
||||||
|
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;
|
||||||
|
|
||||||
|
-- name: DeleteGeometry :exec
|
||||||
|
UPDATE geometries
|
||||||
|
SET
|
||||||
|
is_deleted = true
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: SearchGeometries :many
|
||||||
|
SELECT
|
||||||
|
g.id, g.geo_type, g.draw_geometry, g.binding, g.time_start, g.time_end,
|
||||||
|
ST_XMin(g.bbox)::float8 as min_lng,
|
||||||
|
ST_YMin(g.bbox)::float8 as min_lat,
|
||||||
|
ST_XMax(g.bbox)::float8 as max_lng,
|
||||||
|
ST_YMax(g.bbox)::float8 as max_lat,
|
||||||
|
g.is_deleted, g.created_at, g.updated_at
|
||||||
|
FROM geometries g
|
||||||
|
WHERE g.is_deleted = false
|
||||||
|
AND (
|
||||||
|
sqlc.narg('search_min_lng')::float8 IS NULL OR
|
||||||
|
sqlc.narg('search_min_lat')::float8 IS NULL OR
|
||||||
|
sqlc.narg('search_max_lng')::float8 IS NULL OR
|
||||||
|
sqlc.narg('search_max_lat')::float8 IS NULL OR
|
||||||
|
g.bbox && ST_MakeEnvelope(
|
||||||
|
sqlc.narg('search_min_lng')::float8,
|
||||||
|
sqlc.narg('search_min_lat')::float8,
|
||||||
|
sqlc.narg('search_max_lng')::float8,
|
||||||
|
sqlc.narg('search_max_lat')::float8,
|
||||||
|
4326
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
sqlc.narg('entity_id')::uuid IS NULL OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM entity_geometries eg
|
||||||
|
WHERE eg.geometry_id = g.id
|
||||||
|
AND eg.entity_id = sqlc.narg('entity_id')::uuid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY g.id DESC;
|
||||||
|
|
||||||
|
-- name: BulkDeleteEntityGeometriesByEntityId :many
|
||||||
|
DELETE FROM entity_geometries
|
||||||
|
WHERE entity_id = $1
|
||||||
|
RETURNING geometry_id;
|
||||||
|
|
||||||
|
-- name: CreateEntityGeometries :exec
|
||||||
|
INSERT INTO entity_geometries (
|
||||||
|
entity_id, geometry_id
|
||||||
|
)
|
||||||
|
SELECT $1, unnest(@geometry_ids::uuid[]);
|
||||||
56
db/query/wiki.sql
Normal file
56
db/query/wiki.sql
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
-- name: CreateWiki :one
|
||||||
|
INSERT INTO wikis (
|
||||||
|
title, content
|
||||||
|
) VALUES (
|
||||||
|
$1, $2
|
||||||
|
)
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetWikiById :one
|
||||||
|
SELECT *
|
||||||
|
FROM wikis
|
||||||
|
WHERE id = $1 AND is_deleted = false;
|
||||||
|
|
||||||
|
-- name: UpdateWiki :one
|
||||||
|
UPDATE wikis
|
||||||
|
SET
|
||||||
|
title = COALESCE(sqlc.narg('title'), title),
|
||||||
|
content = COALESCE(sqlc.narg('content'), content)
|
||||||
|
WHERE id = sqlc.arg('id') AND is_deleted = false
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: DeleteWiki :exec
|
||||||
|
UPDATE wikis
|
||||||
|
SET
|
||||||
|
is_deleted = true
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: SearchWikis :many
|
||||||
|
SELECT w.*
|
||||||
|
FROM wikis w
|
||||||
|
WHERE w.is_deleted = false
|
||||||
|
AND w.title ILIKE '%' || sqlc.arg('title')::text || '%'
|
||||||
|
AND (
|
||||||
|
sqlc.narg('entity_id')::uuid IS NULL OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM entity_wikis ew
|
||||||
|
WHERE ew.wiki_id = w.id
|
||||||
|
AND ew.entity_id = sqlc.narg('entity_id')::uuid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND (sqlc.narg('cursor_id')::uuid IS NULL OR w.id < sqlc.narg('cursor_id')::uuid)
|
||||||
|
|
||||||
|
ORDER BY w.id DESC
|
||||||
|
LIMIT sqlc.arg('limit_count');
|
||||||
|
|
||||||
|
-- name: BulkDeleteEntityWikisByEntityId :many
|
||||||
|
DELETE FROM entity_wikis
|
||||||
|
WHERE entity_id = $1
|
||||||
|
RETURNING wiki_id;
|
||||||
|
|
||||||
|
-- name: CreateEntityWikis :exec
|
||||||
|
INSERT INTO entity_wikis (
|
||||||
|
entity_id, wiki_id
|
||||||
|
)
|
||||||
|
SELECT $1, unnest(@wiki_ids::uuid[]);
|
||||||
@@ -69,3 +69,48 @@ CREATE TABLE IF NOT EXISTS verification_medias (
|
|||||||
media_id UUID REFERENCES medias(id) ON DELETE CASCADE,
|
media_id UUID REFERENCES medias(id) ON DELETE CASCADE,
|
||||||
PRIMARY KEY (verification_id, media_id)
|
PRIMARY KEY (verification_id, media_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS entities (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
thumbnail_url TEXT,
|
||||||
|
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS wikis (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||||
|
title TEXT,
|
||||||
|
content TEXT,
|
||||||
|
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS entity_wikis (
|
||||||
|
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
||||||
|
wiki_id UUID REFERENCES wikis(id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (entity_id, wiki_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS geometries (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||||
|
geo_type VARCHAR(50) NOT NULL DEFAULT 'id',
|
||||||
|
draw_geometry JSONB NOT NULL,
|
||||||
|
binding JSONB,
|
||||||
|
time_start INT,
|
||||||
|
time_end INT,
|
||||||
|
bbox GEOMETRY,
|
||||||
|
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS entity_geometries (
|
||||||
|
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
||||||
|
geometry_id UUID REFERENCES geometries(id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (entity_id, geometry_id)
|
||||||
|
);
|
||||||
290
docs/docs.go
290
docs/docs.go
@@ -399,6 +399,204 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/entities": {
|
||||||
|
"get": {
|
||||||
|
"description": "Search entities with cursor pagination",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Entities"
|
||||||
|
],
|
||||||
|
"summary": "Search entities",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "cursor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxLength": 255,
|
||||||
|
"type": "string",
|
||||||
|
"name": "name",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/entities/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get detailed information about a specific entity",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Entities"
|
||||||
|
],
|
||||||
|
"summary": "Get entity by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Entity ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/geometries": {
|
||||||
|
"get": {
|
||||||
|
"description": "Search geometries with cursor pagination and spatial filtering",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Geometries"
|
||||||
|
],
|
||||||
|
"summary": "Search geometries",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "entity_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 90,
|
||||||
|
"minimum": -90,
|
||||||
|
"type": "number",
|
||||||
|
"name": "maxLat",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 180,
|
||||||
|
"minimum": -180,
|
||||||
|
"type": "number",
|
||||||
|
"name": "maxLng",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 90,
|
||||||
|
"minimum": -90,
|
||||||
|
"type": "number",
|
||||||
|
"name": "minLat",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 180,
|
||||||
|
"minimum": -180,
|
||||||
|
"type": "number",
|
||||||
|
"name": "minLng",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"name": "time",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/geometries/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get detailed information about a specific geometry",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Geometries"
|
||||||
|
],
|
||||||
|
"summary": "Get geometry by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Geometry ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/historian/application": {
|
"/historian/application": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1933,6 +2131,98 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/wikis": {
|
||||||
|
"get": {
|
||||||
|
"description": "Search wikis with cursor pagination",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Wikis"
|
||||||
|
],
|
||||||
|
"summary": "Search wikis",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "cursor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "entity_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxLength": 1000,
|
||||||
|
"type": "string",
|
||||||
|
"name": "title",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/wikis/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get detailed information about a specific wiki",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Wikis"
|
||||||
|
],
|
||||||
|
"summary": "Get wiki by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Wiki ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|||||||
@@ -392,6 +392,204 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/entities": {
|
||||||
|
"get": {
|
||||||
|
"description": "Search entities with cursor pagination",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Entities"
|
||||||
|
],
|
||||||
|
"summary": "Search entities",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "cursor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxLength": 255,
|
||||||
|
"type": "string",
|
||||||
|
"name": "name",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/entities/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get detailed information about a specific entity",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Entities"
|
||||||
|
],
|
||||||
|
"summary": "Get entity by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Entity ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/geometries": {
|
||||||
|
"get": {
|
||||||
|
"description": "Search geometries with cursor pagination and spatial filtering",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Geometries"
|
||||||
|
],
|
||||||
|
"summary": "Search geometries",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "entity_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 90,
|
||||||
|
"minimum": -90,
|
||||||
|
"type": "number",
|
||||||
|
"name": "maxLat",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 180,
|
||||||
|
"minimum": -180,
|
||||||
|
"type": "number",
|
||||||
|
"name": "maxLng",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 90,
|
||||||
|
"minimum": -90,
|
||||||
|
"type": "number",
|
||||||
|
"name": "minLat",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 180,
|
||||||
|
"minimum": -180,
|
||||||
|
"type": "number",
|
||||||
|
"name": "minLng",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"name": "time",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/geometries/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get detailed information about a specific geometry",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Geometries"
|
||||||
|
],
|
||||||
|
"summary": "Get geometry by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Geometry ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/historian/application": {
|
"/historian/application": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1926,6 +2124,98 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/wikis": {
|
||||||
|
"get": {
|
||||||
|
"description": "Search wikis with cursor pagination",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Wikis"
|
||||||
|
],
|
||||||
|
"summary": "Search wikis",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "cursor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "entity_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maximum": 100,
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxLength": 1000,
|
||||||
|
"type": "string",
|
||||||
|
"name": "title",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/wikis/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Get detailed information about a specific wiki",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Wikis"
|
||||||
|
],
|
||||||
|
"summary": "Get wiki by ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Wiki ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|||||||
@@ -493,6 +493,138 @@ paths:
|
|||||||
summary: Verify a security token
|
summary: Verify a security token
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
|
/entities:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Search entities with cursor pagination
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: cursor
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
maximum: 100
|
||||||
|
minimum: 1
|
||||||
|
name: limit
|
||||||
|
type: integer
|
||||||
|
- in: query
|
||||||
|
maxLength: 255
|
||||||
|
name: name
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
summary: Search entities
|
||||||
|
tags:
|
||||||
|
- Entities
|
||||||
|
/entities/{id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get detailed information about a specific entity
|
||||||
|
parameters:
|
||||||
|
- description: Entity ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
summary: Get entity by ID
|
||||||
|
tags:
|
||||||
|
- Entities
|
||||||
|
/geometries:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Search geometries with cursor pagination and spatial filtering
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: entity_id
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
maximum: 90
|
||||||
|
minimum: -90
|
||||||
|
name: maxLat
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
- in: query
|
||||||
|
maximum: 180
|
||||||
|
minimum: -180
|
||||||
|
name: maxLng
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
- in: query
|
||||||
|
maximum: 90
|
||||||
|
minimum: -90
|
||||||
|
name: minLat
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
- in: query
|
||||||
|
maximum: 180
|
||||||
|
minimum: -180
|
||||||
|
name: minLng
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
- in: query
|
||||||
|
name: time
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
summary: Search geometries
|
||||||
|
tags:
|
||||||
|
- Geometries
|
||||||
|
/geometries/{id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get detailed information about a specific geometry
|
||||||
|
parameters:
|
||||||
|
- description: Geometry ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
summary: Get geometry by ID
|
||||||
|
tags:
|
||||||
|
- Geometries
|
||||||
/historian/application:
|
/historian/application:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -1480,6 +1612,66 @@ paths:
|
|||||||
summary: Change user password
|
summary: Change user password
|
||||||
tags:
|
tags:
|
||||||
- Users
|
- Users
|
||||||
|
/wikis:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Search wikis with cursor pagination
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: cursor
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: entity_id
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
maximum: 100
|
||||||
|
minimum: 1
|
||||||
|
name: limit
|
||||||
|
type: integer
|
||||||
|
- in: query
|
||||||
|
maxLength: 1000
|
||||||
|
name: title
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
summary: Search wikis
|
||||||
|
tags:
|
||||||
|
- Wikis
|
||||||
|
/wikis/{id}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get detailed information about a specific wiki
|
||||||
|
parameters:
|
||||||
|
- description: Wiki ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||||
|
summary: Get wiki by ID
|
||||||
|
tags:
|
||||||
|
- Wikis
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
BearerAuth:
|
BearerAuth:
|
||||||
description: Type "Bearer " followed by a space and JWT token.
|
description: Type "Bearer " followed by a space and JWT token.
|
||||||
|
|||||||
83
internal/controllers/entityController.go
Normal file
83
internal/controllers/entityController.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"history-api/internal/dtos/request"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/services"
|
||||||
|
"history-api/pkg/validator"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityController struct {
|
||||||
|
service services.EntityService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntityController(svc services.EntityService) *EntityController {
|
||||||
|
return &EntityController{service: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntityById handles fetching a single entity by ID.
|
||||||
|
// @Summary Get entity by ID
|
||||||
|
// @Description Get detailed information about a specific entity
|
||||||
|
// @Tags Entities
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Entity ID"
|
||||||
|
// @Success 200 {object} response.CommonResponse
|
||||||
|
// @Failure 500 {object} response.CommonResponse
|
||||||
|
// @Router /entities/{id} [get]
|
||||||
|
func (h *EntityController) GetEntityById(c fiber.Ctx) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
id := c.Params("id")
|
||||||
|
res, err := h.service.GetEntityByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||||
|
Status: true,
|
||||||
|
Data: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchEntities handles searching for entities.
|
||||||
|
// @Summary Search entities
|
||||||
|
// @Description Search entities with cursor pagination
|
||||||
|
// @Tags Entities
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param query query request.SearchEntityDto false "Search Query"
|
||||||
|
// @Success 200 {object} response.CommonResponse
|
||||||
|
// @Failure 500 {object} response.CommonResponse
|
||||||
|
// @Router /entities [get]
|
||||||
|
func (h *EntityController) SearchEntities(c fiber.Ctx) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
dto := &request.SearchEntityDto{}
|
||||||
|
if err := validator.ValidateQueryDto(c, dto); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Errors: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.service.SearchEntities(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||||
|
Status: true,
|
||||||
|
Data: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
83
internal/controllers/geometryController.go
Normal file
83
internal/controllers/geometryController.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"history-api/internal/dtos/request"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/services"
|
||||||
|
"history-api/pkg/validator"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeometryController struct {
|
||||||
|
service services.GeometryService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeometryController(svc services.GeometryService) *GeometryController {
|
||||||
|
return &GeometryController{service: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGeometryById handles fetching a single geometry by ID.
|
||||||
|
// @Summary Get geometry by ID
|
||||||
|
// @Description Get detailed information about a specific geometry
|
||||||
|
// @Tags Geometries
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Geometry ID"
|
||||||
|
// @Success 200 {object} response.CommonResponse
|
||||||
|
// @Failure 500 {object} response.CommonResponse
|
||||||
|
// @Router /geometries/{id} [get]
|
||||||
|
func (h *GeometryController) GetGeometryById(c fiber.Ctx) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
id := c.Params("id")
|
||||||
|
res, err := h.service.GetGeometryByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||||
|
Status: true,
|
||||||
|
Data: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchGeometries handles searching for geometries.
|
||||||
|
// @Summary Search geometries
|
||||||
|
// @Description Search geometries with cursor pagination and spatial filtering
|
||||||
|
// @Tags Geometries
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param query query request.SearchGeometryDto false "Search Query"
|
||||||
|
// @Success 200 {object} response.CommonResponse
|
||||||
|
// @Failure 500 {object} response.CommonResponse
|
||||||
|
// @Router /geometries [get]
|
||||||
|
func (h *GeometryController) SearchGeometries(c fiber.Ctx) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
dto := &request.SearchGeometryDto{}
|
||||||
|
if err := validator.ValidateQueryDto(c, dto); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Errors: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.service.SearchGeometries(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||||
|
Status: true,
|
||||||
|
Data: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
83
internal/controllers/wikiController.go
Normal file
83
internal/controllers/wikiController.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"history-api/internal/dtos/request"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/services"
|
||||||
|
"history-api/pkg/validator"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WikiController struct {
|
||||||
|
service services.WikiService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWikiController(svc services.WikiService) *WikiController {
|
||||||
|
return &WikiController{service: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWikiById handles fetching a single wiki by ID.
|
||||||
|
// @Summary Get wiki by ID
|
||||||
|
// @Description Get detailed information about a specific wiki
|
||||||
|
// @Tags Wikis
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Wiki ID"
|
||||||
|
// @Success 200 {object} response.CommonResponse
|
||||||
|
// @Failure 500 {object} response.CommonResponse
|
||||||
|
// @Router /wikis/{id} [get]
|
||||||
|
func (h *WikiController) GetWikiById(c fiber.Ctx) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
id := c.Params("id")
|
||||||
|
res, err := h.service.GetWikiByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||||
|
Status: true,
|
||||||
|
Data: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchWikis handles searching for wikis.
|
||||||
|
// @Summary Search wikis
|
||||||
|
// @Description Search wikis with cursor pagination
|
||||||
|
// @Tags Wikis
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param query query request.SearchWikiDto false "Search Query"
|
||||||
|
// @Success 200 {object} response.CommonResponse
|
||||||
|
// @Failure 500 {object} response.CommonResponse
|
||||||
|
// @Router /wikis [get]
|
||||||
|
func (h *WikiController) SearchWikis(c fiber.Ctx) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
dto := &request.SearchWikiDto{}
|
||||||
|
if err := validator.ValidateQueryDto(c, dto); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Errors: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.service.SearchWikis(ctx, dto)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
|
||||||
|
Status: false,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||||
|
Status: true,
|
||||||
|
Data: res,
|
||||||
|
})
|
||||||
|
}
|
||||||
7
internal/dtos/request/entity.go
Normal file
7
internal/dtos/request/entity.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
10
internal/dtos/request/geometry.go
Normal file
10
internal/dtos/request/geometry.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
type SearchGeometryDto struct {
|
||||||
|
MinLng *float64 `query:"min_lng" validate:"required,gte=-180,lte=180"`
|
||||||
|
MinLat *float64 `query:"min_lat" validate:"required,gte=-90,lte=90"`
|
||||||
|
MaxLng *float64 `query:"max_lng" validate:"required,gte=-180,lte=180"`
|
||||||
|
MaxLat *float64 `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"`
|
||||||
|
}
|
||||||
@@ -5,13 +5,14 @@ import "time"
|
|||||||
type UpdateProfileDto struct {
|
type UpdateProfileDto struct {
|
||||||
DisplayName *string `json:"display_name" validate:"omitempty,min=2,max=50"`
|
DisplayName *string `json:"display_name" validate:"omitempty,min=2,max=50"`
|
||||||
FullName *string `json:"full_name" validate:"omitempty,min=2,max=100"`
|
FullName *string `json:"full_name" validate:"omitempty,min=2,max=100"`
|
||||||
AvatarUrl *string `json:"avatar_url" validate:"omitempty,url,image_url"`
|
AvatarUrl *string `json:"avatar_url" validate:"omitempty,image_url"`
|
||||||
Bio *string `json:"bio" validate:"omitempty,max=255"`
|
Bio *string `json:"bio" validate:"omitempty,max=255"`
|
||||||
Location *string `json:"location" validate:"omitempty,max=100"`
|
Location *string `json:"location" validate:"omitempty,max=100"`
|
||||||
Website *string `json:"website" validate:"omitempty,url"`
|
Website *string `json:"website" validate:"omitempty,optional_url"`
|
||||||
CountryCode *string `json:"country_code" validate:"omitempty,len=2"`
|
CountryCode *string `json:"country_code" validate:"omitempty,len=2"`
|
||||||
Phone *string `json:"phone" validate:"omitempty,min=8,max=20"`
|
Phone *string `json:"phone" validate:"omitempty,min=8,max=20"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangePasswordDto struct {
|
type ChangePasswordDto struct {
|
||||||
OldPassword string `json:"old_password" validate:"required,min=8,max=64"`
|
OldPassword string `json:"old_password" validate:"required,min=8,max=64"`
|
||||||
NewPassword string `json:"new_password" validate:"required,min=8,max=64,nefield=OldPassword"`
|
NewPassword string `json:"new_password" validate:"required,min=8,max=64,nefield=OldPassword"`
|
||||||
@@ -26,6 +27,7 @@ type PaginationDto struct {
|
|||||||
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
|
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
|
||||||
Order string `json:"order" query:"order" validate:"omitempty,oneof=asc desc"`
|
Order string `json:"order" query:"order" validate:"omitempty,oneof=asc desc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchUserDto struct {
|
type SearchUserDto struct {
|
||||||
PaginationDto
|
PaginationDto
|
||||||
Sort string `json:"sort" query:"sort" validate:"omitempty,oneof=id created_at updated_at email is_deleted auth_provider"`
|
Sort string `json:"sort" query:"sort" validate:"omitempty,oneof=id created_at updated_at email is_deleted auth_provider"`
|
||||||
|
|||||||
8
internal/dtos/request/wiki.go
Normal file
8
internal/dtos/request/wiki.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package response
|
package response
|
||||||
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token,omitempty"`
|
||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyTokenResponse struct {
|
type VerifyTokenResponse struct {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
type CommonResponse struct {
|
type CommonResponse struct {
|
||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
Data any `json:"data"`
|
Data any `json:"data,omitempty"`
|
||||||
Errors any `json:"errors"`
|
Errors any `json:"errors,omitempty"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JWTClaims struct {
|
type JWTClaims struct {
|
||||||
@@ -29,10 +29,10 @@ type PaginationMeta struct {
|
|||||||
|
|
||||||
type PaginatedResponse struct {
|
type PaginatedResponse struct {
|
||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message,omitempty"`
|
||||||
Data any `json:"data"`
|
Data any `json:"data,omitempty"`
|
||||||
Errors any `json:"errors"`
|
Errors any `json:"errors,omitempty"`
|
||||||
Pagination *PaginationMeta `json:"pagination"`
|
Pagination *PaginationMeta `json:"pagination,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildPaginatedResponse(data any, totalRecords int64, page int, limit int) *PaginatedResponse {
|
func BuildPaginatedResponse(data any, totalRecords int64, page int, limit int) *PaginatedResponse {
|
||||||
|
|||||||
13
internal/dtos/response/entity.go
Normal file
13
internal/dtos/response/entity.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type EntityResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
26
internal/dtos/response/geometry.go
Normal file
26
internal/dtos/response/geometry.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bbox struct {
|
||||||
|
MinLng float64 `json:"min_lng"`
|
||||||
|
MinLat float64 `json:"min_lat"`
|
||||||
|
MaxLng float64 `json:"max_lng"`
|
||||||
|
MaxLat float64 `json:"max_lat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeometryResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
GeoType string `json:"geo_type"`
|
||||||
|
DrawGeometry json.RawMessage `json:"draw_geometry"`
|
||||||
|
Binding json.RawMessage `json:"binding,omitempty"`
|
||||||
|
TimeStart int32 `json:"time_start,omitempty"`
|
||||||
|
TimeEnd int32 `json:"time_end,omitempty"`
|
||||||
|
Bbox *Bbox `json:"bbox,omitempty"`
|
||||||
|
IsDeleted bool `json:"is_deleted,omitempty"`
|
||||||
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
@@ -20,8 +20,8 @@ type MediaResponse struct {
|
|||||||
MimeType string `json:"mime_type"`
|
MimeType string `json:"mime_type"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
FileMetadata json.RawMessage `json:"file_metadata"`
|
FileMetadata json.RawMessage `json:"file_metadata"`
|
||||||
CreatedAt *time.Time `json:"created_at"`
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MediaSimpleResponse struct {
|
type MediaSimpleResponse struct {
|
||||||
@@ -31,5 +31,5 @@ type MediaSimpleResponse struct {
|
|||||||
MimeType string `json:"mime_type"`
|
MimeType string `json:"mime_type"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
FileMetadata json.RawMessage `json:"file_metadata"`
|
FileMetadata json.RawMessage `json:"file_metadata"`
|
||||||
CreatedAt *time.Time `json:"created_at"`
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type RoleSimpleResponse struct {
|
|||||||
type RoleResponse struct {
|
type RoleResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsDeleted bool `json:"is_deleted"`
|
IsDeleted bool `json:"is_deleted,omitempty"`
|
||||||
CreatedAt *time.Time `json:"created_at"`
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,29 +5,29 @@ import "time"
|
|||||||
type UserResponse struct {
|
type UserResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Profile *UserProfileSimpleResponse `json:"profile"`
|
Profile *UserProfileSimpleResponse `json:"profile,omitempty"`
|
||||||
TokenVersion int32 `json:"token_version"`
|
TokenVersion int32 `json:"token_version,omitempty"`
|
||||||
IsDeleted bool `json:"is_deleted"`
|
IsDeleted bool `json:"is_deleted,omitempty"`
|
||||||
CreatedAt *time.Time `json:"created_at"`
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
Roles []*RoleSimpleResponse `json:"roles"`
|
Roles []*RoleSimpleResponse `json:"roles,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSimpleResponse struct {
|
type UserSimpleResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name,omitempty"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
AvatarUrl string `json:"avatar_url"`
|
AvatarUrl string `json:"avatar_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserProfileSimpleResponse struct {
|
type UserProfileSimpleResponse struct {
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
AvatarUrl string `json:"avatar_url"`
|
AvatarUrl string `json:"avatar_url,omitempty"`
|
||||||
Bio string `json:"bio"`
|
Bio string `json:"bio,omitempty"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location,omitempty"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website,omitempty"`
|
||||||
CountryCode string `json:"country_code"`
|
CountryCode string `json:"country_code,omitempty"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import "time"
|
|||||||
|
|
||||||
type UserVerificationResponse struct {
|
type UserVerificationResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
User *UserSimpleResponse `json:"user"`
|
User *UserSimpleResponse `json:"user,omitempty"`
|
||||||
VerifyType string `json:"verify_type"`
|
VerifyType string `json:"verify_type"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Reviewer *UserSimpleResponse `json:"reviewer"`
|
Reviewer *UserSimpleResponse `json:"reviewer,omitempty"`
|
||||||
ReviewNote string `json:"review_note"`
|
ReviewNote string `json:"review_note,omitempty"`
|
||||||
ReviewedAt *time.Time `json:"reviewed_at"`
|
ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
|
||||||
CreatedAt *time.Time `json:"created_at"`
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||||
Medias []*MediaSimpleResponse `json:"media"`
|
Medias []*MediaSimpleResponse `json:"media,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
12
internal/dtos/response/wiki.go
Normal file
12
internal/dtos/response/wiki.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package response
|
||||||
|
|
||||||
|
import "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"`
|
||||||
|
}
|
||||||
156
internal/gen/sqlc/entities.sql.go
Normal file
156
internal/gen/sqlc/entities.sql.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: entities.sql
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createEntity = `-- name: CreateEntity :one
|
||||||
|
INSERT INTO entities (
|
||||||
|
name, description, thumbnail_url
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3
|
||||||
|
)
|
||||||
|
RETURNING id, name, description, thumbnail_url, is_deleted, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateEntityParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
ThumbnailUrl pgtype.Text `json:"thumbnail_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateEntity(ctx context.Context, arg CreateEntityParams) (Entity, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createEntity, arg.Name, arg.Description, arg.ThumbnailUrl)
|
||||||
|
var i Entity
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.ThumbnailUrl,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteEntity = `-- name: DeleteEntity :exec
|
||||||
|
UPDATE entities
|
||||||
|
SET
|
||||||
|
is_deleted = true
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteEntity(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteEntity, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEntityById = `-- name: GetEntityById :one
|
||||||
|
SELECT id, name, description, thumbnail_url, is_deleted, created_at, updated_at
|
||||||
|
FROM entities
|
||||||
|
WHERE id = $1 AND is_deleted = false
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetEntityById(ctx context.Context, id pgtype.UUID) (Entity, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getEntityById, id)
|
||||||
|
var i Entity
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.ThumbnailUrl,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchEntities = `-- name: SearchEntities :many
|
||||||
|
SELECT id, name, description, thumbnail_url, 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)
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type SearchEntitiesParams struct {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []Entity{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i Entity
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.ThumbnailUrl,
|
||||||
|
&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 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
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateEntityParams struct {
|
||||||
|
Name pgtype.Text `json:"name"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
ThumbnailUrl pgtype.Text `json:"thumbnail_url"`
|
||||||
|
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.Description,
|
||||||
|
arg.ThumbnailUrl,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i Entity
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Description,
|
||||||
|
&i.ThumbnailUrl,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
371
internal/gen/sqlc/geometries.sql.go
Normal file
371
internal/gen/sqlc/geometries.sql.go
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: geometries.sql
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bulkDeleteEntityGeometriesByEntityId = `-- name: BulkDeleteEntityGeometriesByEntityId :many
|
||||||
|
DELETE FROM entity_geometries
|
||||||
|
WHERE entity_id = $1
|
||||||
|
RETURNING geometry_id
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) BulkDeleteEntityGeometriesByEntityId(ctx context.Context, entityID pgtype.UUID) ([]pgtype.UUID, error) {
|
||||||
|
rows, err := q.db.Query(ctx, bulkDeleteEntityGeometriesByEntityId, entityID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []pgtype.UUID{}
|
||||||
|
for rows.Next() {
|
||||||
|
var geometry_id pgtype.UUID
|
||||||
|
if err := rows.Scan(&geometry_id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, geometry_id)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const createEntityGeometries = `-- name: CreateEntityGeometries :exec
|
||||||
|
INSERT INTO entity_geometries (
|
||||||
|
entity_id, geometry_id
|
||||||
|
)
|
||||||
|
SELECT $1, unnest($2::uuid[])
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateEntityGeometriesParams struct {
|
||||||
|
EntityID pgtype.UUID `json:"entity_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)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createGeometry = `-- name: CreateGeometry :one
|
||||||
|
INSERT INTO geometries (
|
||||||
|
geo_type, draw_geometry, binding, time_start, time_end, bbox
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, ST_MakeEnvelope($6::float8, $7::float8, $8::float8, $9::float8, 4326)
|
||||||
|
)
|
||||||
|
RETURNING id, geo_type, draw_geometry, binding, time_start, time_end,
|
||||||
|
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
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateGeometryParams struct {
|
||||||
|
GeoType string `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"`
|
||||||
|
MinLng float64 `json:"min_lng"`
|
||||||
|
MinLat float64 `json:"min_lat"`
|
||||||
|
MaxLng float64 `json:"max_lng"`
|
||||||
|
MaxLat float64 `json:"max_lat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateGeometryRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
GeoType string `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"`
|
||||||
|
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) CreateGeometry(ctx context.Context, arg CreateGeometryParams) (CreateGeometryRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createGeometry,
|
||||||
|
arg.GeoType,
|
||||||
|
arg.DrawGeometry,
|
||||||
|
arg.Binding,
|
||||||
|
arg.TimeStart,
|
||||||
|
arg.TimeEnd,
|
||||||
|
arg.MinLng,
|
||||||
|
arg.MinLat,
|
||||||
|
arg.MaxLng,
|
||||||
|
arg.MaxLat,
|
||||||
|
)
|
||||||
|
var i CreateGeometryRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.GeoType,
|
||||||
|
&i.DrawGeometry,
|
||||||
|
&i.Binding,
|
||||||
|
&i.TimeStart,
|
||||||
|
&i.TimeEnd,
|
||||||
|
&i.MinLng,
|
||||||
|
&i.MinLat,
|
||||||
|
&i.MaxLng,
|
||||||
|
&i.MaxLat,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteGeometry = `-- name: DeleteGeometry :exec
|
||||||
|
UPDATE geometries
|
||||||
|
SET
|
||||||
|
is_deleted = true
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteGeometry(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteGeometry, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGeometryById = `-- name: GetGeometryById :one
|
||||||
|
SELECT id, geo_type, draw_geometry, binding, time_start, time_end,
|
||||||
|
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 id = $1 AND is_deleted = false
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetGeometryByIdRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
GeoType string `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"`
|
||||||
|
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) GetGeometryById(ctx context.Context, id pgtype.UUID) (GetGeometryByIdRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getGeometryById, id)
|
||||||
|
var i GetGeometryByIdRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.GeoType,
|
||||||
|
&i.DrawGeometry,
|
||||||
|
&i.Binding,
|
||||||
|
&i.TimeStart,
|
||||||
|
&i.TimeEnd,
|
||||||
|
&i.MinLng,
|
||||||
|
&i.MinLat,
|
||||||
|
&i.MaxLng,
|
||||||
|
&i.MaxLat,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchGeometries = `-- name: SearchGeometries :many
|
||||||
|
SELECT
|
||||||
|
g.id, g.geo_type, g.draw_geometry, g.binding, g.time_start, g.time_end,
|
||||||
|
ST_XMin(g.bbox)::float8 as min_lng,
|
||||||
|
ST_YMin(g.bbox)::float8 as min_lat,
|
||||||
|
ST_XMax(g.bbox)::float8 as max_lng,
|
||||||
|
ST_YMax(g.bbox)::float8 as max_lat,
|
||||||
|
g.is_deleted, g.created_at, g.updated_at
|
||||||
|
FROM geometries g
|
||||||
|
WHERE g.is_deleted = false
|
||||||
|
AND (
|
||||||
|
$1::float8 IS NULL OR
|
||||||
|
$2::float8 IS NULL OR
|
||||||
|
$3::float8 IS NULL OR
|
||||||
|
$4::float8 IS NULL OR
|
||||||
|
g.bbox && ST_MakeEnvelope(
|
||||||
|
$1::float8,
|
||||||
|
$2::float8,
|
||||||
|
$3::float8,
|
||||||
|
$4::float8,
|
||||||
|
4326
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
$5::int IS NULL OR
|
||||||
|
(g.time_start <= $5::int AND g.time_end >= $5::int)
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
$6::uuid IS NULL OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM entity_geometries eg
|
||||||
|
WHERE eg.geometry_id = g.id
|
||||||
|
AND eg.entity_id = $6::uuid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY g.id DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type SearchGeometriesParams struct {
|
||||||
|
SearchMinLng pgtype.Float8 `json:"search_min_lng"`
|
||||||
|
SearchMinLat pgtype.Float8 `json:"search_min_lat"`
|
||||||
|
SearchMaxLng pgtype.Float8 `json:"search_max_lng"`
|
||||||
|
SearchMaxLat pgtype.Float8 `json:"search_max_lat"`
|
||||||
|
TimePoint pgtype.Int4 `json:"time_point"`
|
||||||
|
EntityID pgtype.UUID `json:"entity_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchGeometriesRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
GeoType string `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"`
|
||||||
|
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) SearchGeometries(ctx context.Context, arg SearchGeometriesParams) ([]SearchGeometriesRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, searchGeometries,
|
||||||
|
arg.SearchMinLng,
|
||||||
|
arg.SearchMinLat,
|
||||||
|
arg.SearchMaxLng,
|
||||||
|
arg.SearchMaxLat,
|
||||||
|
arg.TimePoint,
|
||||||
|
arg.EntityID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []SearchGeometriesRow{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i SearchGeometriesRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.GeoType,
|
||||||
|
&i.DrawGeometry,
|
||||||
|
&i.Binding,
|
||||||
|
&i.TimeStart,
|
||||||
|
&i.TimeEnd,
|
||||||
|
&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 updateGeometry = `-- name: UpdateGeometry :one
|
||||||
|
UPDATE geometries
|
||||||
|
SET
|
||||||
|
geo_type = COALESCE($1, geo_type),
|
||||||
|
draw_geometry = COALESCE($2, draw_geometry),
|
||||||
|
binding = COALESCE($3, binding),
|
||||||
|
time_start = COALESCE($4, time_start),
|
||||||
|
time_end = COALESCE($5, time_end),
|
||||||
|
bbox = CASE
|
||||||
|
WHEN $6::boolean = true THEN
|
||||||
|
ST_MakeEnvelope($7::float8, $8::float8, $9::float8, $10::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,
|
||||||
|
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
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateGeometryParams struct {
|
||||||
|
GeoType pgtype.Text `json:"geo_type"`
|
||||||
|
DrawGeometry []byte `json:"draw_geometry"`
|
||||||
|
Binding []byte `json:"binding"`
|
||||||
|
TimeStart pgtype.Int4 `json:"time_start"`
|
||||||
|
TimeEnd pgtype.Int4 `json:"time_end"`
|
||||||
|
UpdateBbox pgtype.Bool `json:"update_bbox"`
|
||||||
|
MinLng pgtype.Float8 `json:"min_lng"`
|
||||||
|
MinLat pgtype.Float8 `json:"min_lat"`
|
||||||
|
MaxLng pgtype.Float8 `json:"max_lng"`
|
||||||
|
MaxLat pgtype.Float8 `json:"max_lat"`
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateGeometryRow struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
GeoType string `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"`
|
||||||
|
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) UpdateGeometry(ctx context.Context, arg UpdateGeometryParams) (UpdateGeometryRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, updateGeometry,
|
||||||
|
arg.GeoType,
|
||||||
|
arg.DrawGeometry,
|
||||||
|
arg.Binding,
|
||||||
|
arg.TimeStart,
|
||||||
|
arg.TimeEnd,
|
||||||
|
arg.UpdateBbox,
|
||||||
|
arg.MinLng,
|
||||||
|
arg.MinLat,
|
||||||
|
arg.MaxLng,
|
||||||
|
arg.MaxLat,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i UpdateGeometryRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.GeoType,
|
||||||
|
&i.DrawGeometry,
|
||||||
|
&i.Binding,
|
||||||
|
&i.TimeStart,
|
||||||
|
&i.TimeEnd,
|
||||||
|
&i.MinLng,
|
||||||
|
&i.MinLat,
|
||||||
|
&i.MaxLng,
|
||||||
|
&i.MaxLat,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
@@ -5,9 +5,44 @@
|
|||||||
package sqlc
|
package sqlc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityGeometry struct {
|
||||||
|
EntityID pgtype.UUID `json:"entity_id"`
|
||||||
|
GeometryID pgtype.UUID `json:"geometry_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntityWiki struct {
|
||||||
|
EntityID pgtype.UUID `json:"entity_id"`
|
||||||
|
WikiID pgtype.UUID `json:"wiki_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Geometry struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
GeoType string `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"`
|
||||||
|
Bbox interface{} `json:"bbox"`
|
||||||
|
IsDeleted bool `json:"is_deleted"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Media struct {
|
type Media struct {
|
||||||
ID pgtype.UUID `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
UserID pgtype.UUID `json:"user_id"`
|
UserID pgtype.UUID `json:"user_id"`
|
||||||
@@ -77,3 +112,12 @@ type VerificationMedia struct {
|
|||||||
VerificationID pgtype.UUID `json:"verification_id"`
|
VerificationID pgtype.UUID `json:"verification_id"`
|
||||||
MediaID pgtype.UUID `json:"media_id"`
|
MediaID pgtype.UUID `json:"media_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Wiki struct {
|
||||||
|
ID pgtype.UUID `json:"id"`
|
||||||
|
Title pgtype.Text `json:"title"`
|
||||||
|
Content pgtype.Text `json:"content"`
|
||||||
|
IsDeleted bool `json:"is_deleted"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|||||||
203
internal/gen/sqlc/wiki.sql.go
Normal file
203
internal/gen/sqlc/wiki.sql.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.30.0
|
||||||
|
// source: wiki.sql
|
||||||
|
|
||||||
|
package sqlc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bulkDeleteEntityWikisByEntityId = `-- name: BulkDeleteEntityWikisByEntityId :many
|
||||||
|
DELETE FROM entity_wikis
|
||||||
|
WHERE entity_id = $1
|
||||||
|
RETURNING wiki_id
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) BulkDeleteEntityWikisByEntityId(ctx context.Context, entityID pgtype.UUID) ([]pgtype.UUID, error) {
|
||||||
|
rows, err := q.db.Query(ctx, bulkDeleteEntityWikisByEntityId, entityID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []pgtype.UUID{}
|
||||||
|
for rows.Next() {
|
||||||
|
var wiki_id pgtype.UUID
|
||||||
|
if err := rows.Scan(&wiki_id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, wiki_id)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const createEntityWikis = `-- name: CreateEntityWikis :exec
|
||||||
|
INSERT INTO entity_wikis (
|
||||||
|
entity_id, wiki_id
|
||||||
|
)
|
||||||
|
SELECT $1, unnest($2::uuid[])
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateEntityWikisParams struct {
|
||||||
|
EntityID pgtype.UUID `json:"entity_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)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createWiki = `-- name: CreateWiki :one
|
||||||
|
INSERT INTO wikis (
|
||||||
|
title, content
|
||||||
|
) VALUES (
|
||||||
|
$1, $2
|
||||||
|
)
|
||||||
|
RETURNING id, title, content, is_deleted, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateWikiParams struct {
|
||||||
|
Title pgtype.Text `json:"title"`
|
||||||
|
Content pgtype.Text `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateWiki(ctx context.Context, arg CreateWikiParams) (Wiki, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createWiki, arg.Title, arg.Content)
|
||||||
|
var i Wiki
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteWiki = `-- name: DeleteWiki :exec
|
||||||
|
UPDATE wikis
|
||||||
|
SET
|
||||||
|
is_deleted = true
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteWiki(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteWiki, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWikiById = `-- name: GetWikiById :one
|
||||||
|
SELECT id, title, content, is_deleted, created_at, updated_at
|
||||||
|
FROM wikis
|
||||||
|
WHERE id = $1 AND is_deleted = false
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetWikiById(ctx context.Context, id pgtype.UUID) (Wiki, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getWikiById, id)
|
||||||
|
var i Wiki
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchWikis = `-- name: SearchWikis :many
|
||||||
|
SELECT w.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 (
|
||||||
|
$2::uuid IS NULL OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM entity_wikis ew
|
||||||
|
WHERE ew.wiki_id = w.id
|
||||||
|
AND ew.entity_id = $2::uuid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ($3::uuid IS NULL OR w.id < $3::uuid)
|
||||||
|
|
||||||
|
ORDER BY w.id DESC
|
||||||
|
LIMIT $4
|
||||||
|
`
|
||||||
|
|
||||||
|
type SearchWikisParams struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
EntityID pgtype.UUID `json:"entity_id"`
|
||||||
|
CursorID pgtype.UUID `json:"cursor_id"`
|
||||||
|
LimitCount int32 `json:"limit_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) SearchWikis(ctx context.Context, arg SearchWikisParams) ([]Wiki, error) {
|
||||||
|
rows, err := q.db.Query(ctx, searchWikis,
|
||||||
|
arg.Title,
|
||||||
|
arg.EntityID,
|
||||||
|
arg.CursorID,
|
||||||
|
arg.LimitCount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []Wiki{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i Wiki
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&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 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
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateWikiParams struct {
|
||||||
|
Title pgtype.Text `json:"title"`
|
||||||
|
Content pgtype.Text `json:"content"`
|
||||||
|
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)
|
||||||
|
var i Wiki
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Title,
|
||||||
|
&i.Content,
|
||||||
|
&i.IsDeleted,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
45
internal/models/entity.go
Normal file
45
internal/models/entity.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityEntity struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ThumbnailUrl string `json:"thumbnail_url"`
|
||||||
|
IsDeleted bool `json:"is_deleted"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EntityEntity) ToResponse() *response.EntityResponse {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &response.EntityResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
Name: e.Name,
|
||||||
|
Description: e.Description,
|
||||||
|
ThumbnailUrl: e.ThumbnailUrl,
|
||||||
|
IsDeleted: e.IsDeleted,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EntitiesEntityToResponse(es []*EntityEntity) []*response.EntityResponse {
|
||||||
|
out := make([]*response.EntityResponse, 0)
|
||||||
|
if es == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
for _, e := range es {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, e.ToResponse())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
52
internal/models/geometry.go
Normal file
52
internal/models/geometry.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeometryEntity struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
GeoType string `json:"geo_type"`
|
||||||
|
DrawGeometry json.RawMessage `json:"draw_geometry"`
|
||||||
|
Binding json.RawMessage `json:"binding"`
|
||||||
|
TimeStart int32 `json:"time_start"`
|
||||||
|
TimeEnd int32 `json:"time_end"`
|
||||||
|
Bbox *response.Bbox `json:"bbox"`
|
||||||
|
IsDeleted bool `json:"is_deleted"`
|
||||||
|
CreatedAt *time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GeometryEntity) ToResponse() *response.GeometryResponse {
|
||||||
|
if g == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &response.GeometryResponse{
|
||||||
|
ID: g.ID,
|
||||||
|
GeoType: g.GeoType,
|
||||||
|
DrawGeometry: g.DrawGeometry,
|
||||||
|
Binding: g.Binding,
|
||||||
|
TimeStart: g.TimeStart,
|
||||||
|
TimeEnd: g.TimeEnd,
|
||||||
|
Bbox: g.Bbox,
|
||||||
|
IsDeleted: g.IsDeleted,
|
||||||
|
CreatedAt: g.CreatedAt,
|
||||||
|
UpdatedAt: g.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GeometriesEntityToResponse(gs []*GeometryEntity) []*response.GeometryResponse {
|
||||||
|
out := make([]*response.GeometryResponse, 0)
|
||||||
|
if gs == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
for _, g := range gs {
|
||||||
|
if g == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, g.ToResponse())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
43
internal/models/wiki.go
Normal file
43
internal/models/wiki.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WikiEntity) ToResponse() *response.WikiResponse {
|
||||||
|
if w == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &response.WikiResponse{
|
||||||
|
ID: w.ID,
|
||||||
|
Title: w.Title,
|
||||||
|
Content: w.Content,
|
||||||
|
IsDeleted: w.IsDeleted,
|
||||||
|
CreatedAt: w.CreatedAt,
|
||||||
|
UpdatedAt: w.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WikisEntityToResponse(ws []*WikiEntity) []*response.WikiResponse {
|
||||||
|
out := make([]*response.WikiResponse, 0)
|
||||||
|
if ws == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
for _, w := range ws {
|
||||||
|
if w == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, w.ToResponse())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
205
internal/repositories/entityRepository.go
Normal file
205
internal/repositories/entityRepository.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
|
||||||
|
"history-api/internal/gen/sqlc"
|
||||||
|
"history-api/internal/models"
|
||||||
|
"history-api/pkg/cache"
|
||||||
|
"history-api/pkg/constants"
|
||||||
|
"history-api/pkg/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityRepository interface {
|
||||||
|
GetByID(ctx context.Context, id pgtype.UUID) (*models.EntityEntity, error)
|
||||||
|
GetByIDs(ctx context.Context, ids []string) ([]*models.EntityEntity, error)
|
||||||
|
Search(ctx context.Context, params sqlc.SearchEntitiesParams) ([]*models.EntityEntity, error)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type entityRepository struct {
|
||||||
|
q *sqlc.Queries
|
||||||
|
c cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntityRepository(db sqlc.DBTX, c cache.Cache) EntityRepository {
|
||||||
|
return &entityRepository{
|
||||||
|
q: sqlc.New(db),
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) generateQueryKey(prefix string, params any) string {
|
||||||
|
b, _ := json.Marshal(params)
|
||||||
|
hash := fmt.Sprintf("%x", md5.Sum(b))
|
||||||
|
return fmt.Sprintf("%s:%s", prefix, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.EntityEntity, error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []*models.EntityEntity{}, nil
|
||||||
|
}
|
||||||
|
keys := make([]string, len(ids))
|
||||||
|
for i, id := range ids {
|
||||||
|
keys[i] = fmt.Sprintf("entity:id:%s", id)
|
||||||
|
}
|
||||||
|
raws := r.c.MGet(ctx, keys...)
|
||||||
|
|
||||||
|
var entities []*models.EntityEntity
|
||||||
|
missingToCache := make(map[string]any)
|
||||||
|
|
||||||
|
for i, b := range raws {
|
||||||
|
if len(b) > 0 {
|
||||||
|
var e models.EntityEntity
|
||||||
|
if err := json.Unmarshal(b, &e); err == nil {
|
||||||
|
entities = append(entities, &e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pgId := pgtype.UUID{}
|
||||||
|
err := pgId.Scan(ids[i])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dbEntity, err := r.GetByID(ctx, pgId)
|
||||||
|
if err == nil && dbEntity != nil {
|
||||||
|
entities = append(entities, dbEntity)
|
||||||
|
missingToCache[keys[i]] = dbEntity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingToCache) > 0 {
|
||||||
|
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) GetByIDs(ctx context.Context, ids []string) ([]*models.EntityEntity, error) {
|
||||||
|
return r.getByIDsWithFallback(ctx, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.EntityEntity, error) {
|
||||||
|
cacheId := fmt.Sprintf("entity:id:%s", convert.UUIDToString(id))
|
||||||
|
var entity models.EntityEntity
|
||||||
|
err := r.c.Get(ctx, cacheId, &entity)
|
||||||
|
if err == nil {
|
||||||
|
_ = r.c.Set(ctx, cacheId, entity, constants.NormalCacheDuration)
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := r.q.GetEntityById(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = models.EntityEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
Name: row.Name,
|
||||||
|
Description: convert.TextToString(row.Description),
|
||||||
|
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, cacheId, entity, constants.NormalCacheDuration)
|
||||||
|
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitiesParams) ([]*models.EntityEntity, error) {
|
||||||
|
queryKey := r.generateQueryKey("entity:search", params)
|
||||||
|
var cachedIDs []string
|
||||||
|
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||||
|
return r.getByIDsWithFallback(ctx, cachedIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.q.SearchEntities(ctx, params)
|
||||||
|
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,
|
||||||
|
Description: convert.TextToString(row.Description),
|
||||||
|
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
|
||||||
|
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, queryKey, ids, constants.ListCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) Create(ctx context.Context, params sqlc.CreateEntityParams) (*models.EntityEntity, error) {
|
||||||
|
row, err := r.q.CreateEntity(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := models.EntityEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
Name: row.Name,
|
||||||
|
Description: convert.TextToString(row.Description),
|
||||||
|
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, fmt.Sprintf("entity:id:%s", entity.ID), entity, constants.NormalCacheDuration)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = r.c.DelByPattern(context.Background(), "entity:search*")
|
||||||
|
}()
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) Update(ctx context.Context, params sqlc.UpdateEntityParams) (*models.EntityEntity, error) {
|
||||||
|
row, err := r.q.UpdateEntity(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entity := models.EntityEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
Name: row.Name,
|
||||||
|
Description: convert.TextToString(row.Description),
|
||||||
|
ThumbnailUrl: convert.TextToString(row.ThumbnailUrl),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, fmt.Sprintf("entity:id:%s", entity.ID), entity, constants.NormalCacheDuration)
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *entityRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
err := r.q.DeleteEntity(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = r.c.Del(ctx, fmt.Sprintf("entity:id:%s", convert.UUIDToString(id)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
266
internal/repositories/geometryRepository.go
Normal file
266
internal/repositories/geometryRepository.go
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/gen/sqlc"
|
||||||
|
"history-api/internal/models"
|
||||||
|
"history-api/pkg/cache"
|
||||||
|
"history-api/pkg/constants"
|
||||||
|
"history-api/pkg/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeometryRepository interface {
|
||||||
|
GetByID(ctx context.Context, id pgtype.UUID) (*models.GeometryEntity, error)
|
||||||
|
GetByIDs(ctx context.Context, ids []string) ([]*models.GeometryEntity, error)
|
||||||
|
Search(ctx context.Context, params sqlc.SearchGeometriesParams) ([]*models.GeometryEntity, error)
|
||||||
|
Create(ctx context.Context, params sqlc.CreateGeometryParams) (*models.GeometryEntity, error)
|
||||||
|
Update(ctx context.Context, params sqlc.UpdateGeometryParams) (*models.GeometryEntity, error)
|
||||||
|
Delete(ctx context.Context, id pgtype.UUID) error
|
||||||
|
CreateEntityGeometries(ctx context.Context, params sqlc.CreateEntityGeometriesParams) error
|
||||||
|
BulkDeleteEntityGeometriesByEntityId(ctx context.Context, entityId pgtype.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type geometryRepository struct {
|
||||||
|
q *sqlc.Queries
|
||||||
|
c cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeometryRepository(db sqlc.DBTX, c cache.Cache) GeometryRepository {
|
||||||
|
return &geometryRepository{
|
||||||
|
q: sqlc.New(db),
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) generateQueryKey(prefix string, params any) string {
|
||||||
|
b, _ := json.Marshal(params)
|
||||||
|
hash := fmt.Sprintf("%x", md5.Sum(b))
|
||||||
|
return fmt.Sprintf("%s:%s", prefix, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.GeometryEntity, error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []*models.GeometryEntity{}, nil
|
||||||
|
}
|
||||||
|
keys := make([]string, len(ids))
|
||||||
|
for i, id := range ids {
|
||||||
|
keys[i] = fmt.Sprintf("geometry:id:%s", id)
|
||||||
|
}
|
||||||
|
raws := r.c.MGet(ctx, keys...)
|
||||||
|
|
||||||
|
var geometries []*models.GeometryEntity
|
||||||
|
missingToCache := make(map[string]any)
|
||||||
|
|
||||||
|
for i, b := range raws {
|
||||||
|
if len(b) > 0 {
|
||||||
|
var g models.GeometryEntity
|
||||||
|
if err := json.Unmarshal(b, &g); err == nil {
|
||||||
|
geometries = append(geometries, &g)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pgId := pgtype.UUID{}
|
||||||
|
err := pgId.Scan(ids[i])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dbGeometry, err := r.GetByID(ctx, pgId)
|
||||||
|
if err == nil && dbGeometry != nil {
|
||||||
|
geometries = append(geometries, dbGeometry)
|
||||||
|
missingToCache[keys[i]] = dbGeometry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingToCache) > 0 {
|
||||||
|
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) GetByIDs(ctx context.Context, ids []string) ([]*models.GeometryEntity, error) {
|
||||||
|
return r.getByIDsWithFallback(ctx, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.GeometryEntity, error) {
|
||||||
|
cacheId := fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id))
|
||||||
|
var geometry models.GeometryEntity
|
||||||
|
err := r.c.Get(ctx, cacheId, &geometry)
|
||||||
|
if err == nil {
|
||||||
|
_ = r.c.Set(ctx, cacheId, geometry, constants.NormalCacheDuration)
|
||||||
|
return &geometry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := r.q.GetGeometryById(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry = models.GeometryEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
GeoType: 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,
|
||||||
|
},
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, cacheId, geometry, constants.NormalCacheDuration)
|
||||||
|
|
||||||
|
return &geometry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeometriesParams) ([]*models.GeometryEntity, error) {
|
||||||
|
queryKey := r.generateQueryKey("geometry:search", params)
|
||||||
|
var cachedIDs []string
|
||||||
|
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||||
|
return r.getByIDsWithFallback(ctx, cachedIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.q.SearchGeometries(ctx, params)
|
||||||
|
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: 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,
|
||||||
|
},
|
||||||
|
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, queryKey, ids, constants.ListCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) Create(ctx context.Context, params sqlc.CreateGeometryParams) (*models.GeometryEntity, error) {
|
||||||
|
row, err := r.q.CreateGeometry(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
geometry := models.GeometryEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
GeoType: 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,
|
||||||
|
},
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, fmt.Sprintf("geometry:id:%s", geometry.ID), geometry, constants.NormalCacheDuration)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
bgCtx := context.Background()
|
||||||
|
_ = r.c.DelByPattern(bgCtx, "geometry:search*")
|
||||||
|
}()
|
||||||
|
return &geometry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) Update(ctx context.Context, params sqlc.UpdateGeometryParams) (*models.GeometryEntity, error) {
|
||||||
|
row, err := r.q.UpdateGeometry(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
geometry := models.GeometryEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
GeoType: 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,
|
||||||
|
},
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, fmt.Sprintf("geometry:id:%s", geometry.ID), geometry, constants.NormalCacheDuration)
|
||||||
|
return &geometry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
err := r.q.DeleteGeometry(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = r.c.Del(ctx, fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) CreateEntityGeometries(ctx context.Context, params sqlc.CreateEntityGeometriesParams) error {
|
||||||
|
err := r.q.CreateEntityGeometries(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *geometryRepository) BulkDeleteEntityGeometriesByEntityId(ctx context.Context, entityId pgtype.UUID) error {
|
||||||
|
geometryIDs, 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 {
|
||||||
|
keys[i] = fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id))
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = r.c.Del(context.Background(), keys...)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -148,6 +148,7 @@ func (r *roleRepository) Create(ctx context.Context, name string) (*models.RoleE
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
_ = r.c.DelByPattern(bgCtx, "role:all*")
|
_ = r.c.DelByPattern(bgCtx, "role:all*")
|
||||||
|
|||||||
@@ -225,8 +225,7 @@ func (v *verificationRepository) BulkVerificationMediaByMediaId(ctx context.Cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
bgCtx := context.Background()
|
_ = v.c.Del(context.Background(), listCacheId...)
|
||||||
_ = v.c.Del(bgCtx, listCacheId...)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
233
internal/repositories/wikiRepository.go
Normal file
233
internal/repositories/wikiRepository.go
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
|
||||||
|
"history-api/internal/gen/sqlc"
|
||||||
|
"history-api/internal/models"
|
||||||
|
"history-api/pkg/cache"
|
||||||
|
"history-api/pkg/constants"
|
||||||
|
"history-api/pkg/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WikiRepository interface {
|
||||||
|
GetByID(ctx context.Context, id pgtype.UUID) (*models.WikiEntity, error)
|
||||||
|
GetByIDs(ctx context.Context, ids []string) ([]*models.WikiEntity, error)
|
||||||
|
Search(ctx context.Context, params sqlc.SearchWikisParams) ([]*models.WikiEntity, error)
|
||||||
|
Create(ctx context.Context, params sqlc.CreateWikiParams) (*models.WikiEntity, error)
|
||||||
|
Update(ctx context.Context, params sqlc.UpdateWikiParams) (*models.WikiEntity, error)
|
||||||
|
Delete(ctx context.Context, id pgtype.UUID) error
|
||||||
|
CreateEntityWikis(ctx context.Context, params sqlc.CreateEntityWikisParams) error
|
||||||
|
BulkDeleteEntityWikisByEntityId(ctx context.Context, entityId pgtype.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type wikiRepository struct {
|
||||||
|
q *sqlc.Queries
|
||||||
|
c cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWikiRepository(db sqlc.DBTX, c cache.Cache) WikiRepository {
|
||||||
|
return &wikiRepository{
|
||||||
|
q: sqlc.New(db),
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) generateQueryKey(prefix string, params any) string {
|
||||||
|
b, _ := json.Marshal(params)
|
||||||
|
hash := fmt.Sprintf("%x", md5.Sum(b))
|
||||||
|
return fmt.Sprintf("%s:%s", prefix, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.WikiEntity, error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []*models.WikiEntity{}, nil
|
||||||
|
}
|
||||||
|
keys := make([]string, len(ids))
|
||||||
|
for i, id := range ids {
|
||||||
|
keys[i] = fmt.Sprintf("wiki:id:%s", id)
|
||||||
|
}
|
||||||
|
raws := r.c.MGet(ctx, keys...)
|
||||||
|
|
||||||
|
var wikis []*models.WikiEntity
|
||||||
|
missingToCache := make(map[string]any)
|
||||||
|
|
||||||
|
for i, b := range raws {
|
||||||
|
if len(b) > 0 {
|
||||||
|
var w models.WikiEntity
|
||||||
|
if err := json.Unmarshal(b, &w); err == nil {
|
||||||
|
wikis = append(wikis, &w)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pgId := pgtype.UUID{}
|
||||||
|
err := pgId.Scan(ids[i])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dbWiki, err := r.GetByID(ctx, pgId)
|
||||||
|
if err == nil && dbWiki != nil {
|
||||||
|
wikis = append(wikis, dbWiki)
|
||||||
|
missingToCache[keys[i]] = dbWiki
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingToCache) > 0 {
|
||||||
|
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wikis, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) GetByIDs(ctx context.Context, ids []string) ([]*models.WikiEntity, error) {
|
||||||
|
return r.getByIDsWithFallback(ctx, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.WikiEntity, error) {
|
||||||
|
cacheId := fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id))
|
||||||
|
var wiki models.WikiEntity
|
||||||
|
err := r.c.Get(ctx, cacheId, &wiki)
|
||||||
|
if err == nil {
|
||||||
|
_ = r.c.Set(ctx, cacheId, wiki, constants.NormalCacheDuration)
|
||||||
|
return &wiki, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
row, err := r.q.GetWikiById(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wiki = models.WikiEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
Title: convert.TextToString(row.Title),
|
||||||
|
Content: convert.TextToString(row.Content),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, cacheId, wiki, constants.NormalCacheDuration)
|
||||||
|
|
||||||
|
return &wiki, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisParams) ([]*models.WikiEntity, error) {
|
||||||
|
queryKey := r.generateQueryKey("wiki:search", params)
|
||||||
|
var cachedIDs []string
|
||||||
|
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||||
|
return r.getByIDsWithFallback(ctx, cachedIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.q.SearchWikis(ctx, params)
|
||||||
|
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: convert.TextToString(row.Content),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
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, queryKey, ids, constants.ListCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wikis, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) Create(ctx context.Context, params sqlc.CreateWikiParams) (*models.WikiEntity, error) {
|
||||||
|
row, err := r.q.CreateWiki(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wiki := models.WikiEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
Title: convert.TextToString(row.Title),
|
||||||
|
Content: convert.TextToString(row.Content),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID), wiki, constants.NormalCacheDuration)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
bgCtx := context.Background()
|
||||||
|
_ = r.c.DelByPattern(bgCtx, "wiki:search*")
|
||||||
|
}()
|
||||||
|
|
||||||
|
return &wiki, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) Update(ctx context.Context, params sqlc.UpdateWikiParams) (*models.WikiEntity, error) {
|
||||||
|
row, err := r.q.UpdateWiki(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wiki := models.WikiEntity{
|
||||||
|
ID: convert.UUIDToString(row.ID),
|
||||||
|
Title: convert.TextToString(row.Title),
|
||||||
|
Content: convert.TextToString(row.Content),
|
||||||
|
IsDeleted: row.IsDeleted,
|
||||||
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID), wiki, constants.NormalCacheDuration)
|
||||||
|
return &wiki, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
err := r.q.DeleteWiki(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = r.c.Del(ctx, fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id)))
|
||||||
|
go func() {
|
||||||
|
_ = r.c.DelByPattern(context.Background(), "wiki:search*")
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wikiRepository) CreateEntityWikis(ctx context.Context, params sqlc.CreateEntityWikisParams) error {
|
||||||
|
err := r.q.CreateEntityWikis(ctx, params)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(wikiIDs) > 0 {
|
||||||
|
keys := make([]string, len(wikiIDs))
|
||||||
|
for i, id := range wikiIDs {
|
||||||
|
keys[i] = fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id))
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = r.c.Del(context.Background(), keys...)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
13
internal/routes/entityRoute.go
Normal file
13
internal/routes/entityRoute.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"history-api/internal/controllers"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupEntityRoutes(router fiber.Router, entityController *controllers.EntityController) {
|
||||||
|
entity := router.Group("/entities")
|
||||||
|
entity.Get("/", entityController.SearchEntities)
|
||||||
|
entity.Get("/:id", entityController.GetEntityById)
|
||||||
|
}
|
||||||
13
internal/routes/geometryRoute.go
Normal file
13
internal/routes/geometryRoute.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"history-api/internal/controllers"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupGeometryRoutes(router fiber.Router, geometryController *controllers.GeometryController) {
|
||||||
|
geometry := router.Group("/geometries")
|
||||||
|
geometry.Get("/", geometryController.SearchGeometries)
|
||||||
|
geometry.Get("/:id", geometryController.GetGeometryById)
|
||||||
|
}
|
||||||
13
internal/routes/wikiRoute.go
Normal file
13
internal/routes/wikiRoute.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"history-api/internal/controllers"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetupWikiRoutes(router fiber.Router, wikiController *controllers.WikiController) {
|
||||||
|
wiki := router.Group("/wikis")
|
||||||
|
wiki.Get("/", wikiController.SearchWikis)
|
||||||
|
wiki.Get("/:id", wikiController.GetWikiById)
|
||||||
|
}
|
||||||
68
internal/services/entityService.go
Normal file
68
internal/services/entityService.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"history-api/internal/dtos/request"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/gen/sqlc"
|
||||||
|
"history-api/internal/models"
|
||||||
|
"history-api/internal/repositories"
|
||||||
|
"history-api/pkg/convert"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityService interface {
|
||||||
|
GetEntityByID(ctx context.Context, id string) (*response.EntityResponse, error)
|
||||||
|
SearchEntities(ctx context.Context, req *request.SearchEntityDto) ([]*response.EntityResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type entityService struct {
|
||||||
|
entityRepo repositories.EntityRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntityService(entityRepo repositories.EntityRepository) EntityService {
|
||||||
|
return &entityService{
|
||||||
|
entityRepo: entityRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *entityService) GetEntityByID(ctx context.Context, id string) (*response.EntityResponse, error) {
|
||||||
|
entityId, err := convert.StringToUUID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
entity, err := s.entityRepo.GetByID(ctx, entityId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Entity not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *entityService) SearchEntities(ctx context.Context, req *request.SearchEntityDto) ([]*response.EntityResponse, error) {
|
||||||
|
limit := int32(25)
|
||||||
|
if req.Limit > 0 {
|
||||||
|
limit = int32(req.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := sqlc.SearchEntitiesParams{
|
||||||
|
LimitCount: limit,
|
||||||
|
}
|
||||||
|
if req.Cursor != "" {
|
||||||
|
cursor, err := convert.StringToUUID(req.Cursor)
|
||||||
|
if err == nil {
|
||||||
|
params.CursorID = cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Name != "" {
|
||||||
|
params.Name = req.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
entities, err := s.entityRepo.Search(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.EntitiesEntityToResponse(entities), nil
|
||||||
|
}
|
||||||
79
internal/services/geometryService.go
Normal file
79
internal/services/geometryService.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"history-api/internal/dtos/request"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/gen/sqlc"
|
||||||
|
"history-api/internal/models"
|
||||||
|
"history-api/internal/repositories"
|
||||||
|
"history-api/pkg/convert"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeometryService interface {
|
||||||
|
GetGeometryByID(ctx context.Context, id string) (*response.GeometryResponse, error)
|
||||||
|
SearchGeometries(ctx context.Context, req *request.SearchGeometryDto) ([]*response.GeometryResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type geometryService struct {
|
||||||
|
geometryRepo repositories.GeometryRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeometryService(geometryRepo repositories.GeometryRepository) GeometryService {
|
||||||
|
return &geometryService{
|
||||||
|
geometryRepo: geometryRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *geometryService) GetGeometryByID(ctx context.Context, id string) (*response.GeometryResponse, error) {
|
||||||
|
geometryId, err := convert.StringToUUID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
geometry, err := s.geometryRepo.GetByID(ctx, geometryId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Geometry not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return geometry.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *geometryService) SearchGeometries(ctx context.Context, req *request.SearchGeometryDto) ([]*response.GeometryResponse, error) {
|
||||||
|
params := sqlc.SearchGeometriesParams{}
|
||||||
|
|
||||||
|
if req.MinLng != nil && req.MinLat != nil && req.MaxLng != nil && req.MaxLat != nil {
|
||||||
|
if *req.MaxLng < *req.MinLng || *req.MaxLat < *req.MinLat {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid bounding box")
|
||||||
|
}
|
||||||
|
params.SearchMinLng = pgtype.Float8{Float64: *req.MinLng, Valid: true}
|
||||||
|
params.SearchMinLat = pgtype.Float8{Float64: *req.MinLat, Valid: true}
|
||||||
|
params.SearchMaxLng = pgtype.Float8{Float64: *req.MaxLng, Valid: true}
|
||||||
|
params.SearchMaxLat = pgtype.Float8{Float64: *req.MaxLat, Valid: true}
|
||||||
|
} else {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Must provid Bounding box!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TimePoint != nil {
|
||||||
|
if *req.TimePoint < 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Time point must be non-negative!")
|
||||||
|
}
|
||||||
|
params.TimePoint = pgtype.Int4{Int32: *req.TimePoint, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.EntityID != nil {
|
||||||
|
entityId, err := convert.StringToUUID(*req.EntityID)
|
||||||
|
if err == nil {
|
||||||
|
params.EntityID = entityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geometries, err := s.geometryRepo.Search(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.GeometriesEntityToResponse(geometries), nil
|
||||||
|
}
|
||||||
@@ -64,8 +64,16 @@ func (u *userService) ChangePassword(ctx context.Context, userId string, dto *re
|
|||||||
return fiber.NewError(fiber.StatusNotFound, "User not found")
|
return fiber.NewError(fiber.StatusNotFound, "User not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.PasswordHash != "" {
|
||||||
|
if dto.OldPassword == "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Old password required")
|
||||||
|
}
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(dto.OldPassword)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(dto.OldPassword)); err != nil {
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid identity or password!")
|
return fiber.NewError(fiber.StatusUnauthorized, "Invalid password!")
|
||||||
|
}
|
||||||
|
} else if user.PasswordHash == "" && dto.OldPassword != "" {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request")
|
||||||
}
|
}
|
||||||
|
|
||||||
hashPassword, err := bcrypt.GenerateFromPassword([]byte(dto.NewPassword), bcrypt.DefaultCost)
|
hashPassword, err := bcrypt.GenerateFromPassword([]byte(dto.NewPassword), bcrypt.DefaultCost)
|
||||||
|
|||||||
74
internal/services/wikiService.go
Normal file
74
internal/services/wikiService.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"history-api/internal/dtos/request"
|
||||||
|
"history-api/internal/dtos/response"
|
||||||
|
"history-api/internal/gen/sqlc"
|
||||||
|
"history-api/internal/models"
|
||||||
|
"history-api/internal/repositories"
|
||||||
|
"history-api/pkg/convert"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WikiService interface {
|
||||||
|
GetWikiByID(ctx context.Context, id string) (*response.WikiResponse, error)
|
||||||
|
SearchWikis(ctx context.Context, req *request.SearchWikiDto) ([]*response.WikiResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wikiService struct {
|
||||||
|
wikiRepo repositories.WikiRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWikiService(wikiRepo repositories.WikiRepository) WikiService {
|
||||||
|
return &wikiService{
|
||||||
|
wikiRepo: wikiRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *wikiService) GetWikiByID(ctx context.Context, id string) (*response.WikiResponse, error) {
|
||||||
|
wikiId, err := convert.StringToUUID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
wiki, err := s.wikiRepo.GetByID(ctx, wikiId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Wiki not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return wiki.ToResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *wikiService) SearchWikis(ctx context.Context, req *request.SearchWikiDto) ([]*response.WikiResponse, error) {
|
||||||
|
limit := int32(25)
|
||||||
|
if req.Limit > 0 {
|
||||||
|
limit = int32(req.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := sqlc.SearchWikisParams{
|
||||||
|
LimitCount: limit,
|
||||||
|
}
|
||||||
|
if req.Cursor != "" {
|
||||||
|
cursor, err := convert.StringToUUID(req.Cursor)
|
||||||
|
if err == nil {
|
||||||
|
params.CursorID = cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Title != "" {
|
||||||
|
params.Title = req.Title
|
||||||
|
}
|
||||||
|
if req.EntityID != "" {
|
||||||
|
entityId, err := convert.StringToUUID(req.EntityID)
|
||||||
|
if err == nil {
|
||||||
|
params.EntityID = entityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wikis, err := s.wikiRepo.Search(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.WikisEntityToResponse(wikis), nil
|
||||||
|
}
|
||||||
@@ -53,3 +53,24 @@ func PtrToText(s *string) pgtype.Text {
|
|||||||
Valid: true,
|
Valid: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TextToPtr(v pgtype.Text) *string {
|
||||||
|
if !v.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &v.String
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int4ToPtr(v pgtype.Int4) *int32 {
|
||||||
|
if !v.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &v.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int4ToInt32(v pgtype.Int4) int32 {
|
||||||
|
if v.Valid {
|
||||||
|
return v.Int32
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,33 @@ func init() {
|
|||||||
}
|
}
|
||||||
return isImageURL(val)
|
return isImageURL(val)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
validate.RegisterValidation("optional_url", func(fl validator.FieldLevel) bool {
|
||||||
|
val := fl.Field().String()
|
||||||
|
if val == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return isValidURL(val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func isValidURL(s string) bool {
|
||||||
|
u, err := url.ParseRequestURI(s)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "http" && u.Scheme != "https" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Host == "" || !strings.Contains(u.Host, ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func isImageURL(u string) bool {
|
func isImageURL(u string) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user