feat: implement battle replay module with database migrations, repository, and CRUD service endpoints
All checks were successful
Build and Release / release (push) Successful in 1m32s
All checks were successful
Build and Release / release (push) Successful in 1m32s
This commit is contained in:
8902
FinalProject.drawio
Normal file
8902
FinalProject.drawio
Normal file
File diff suppressed because it is too large
Load Diff
@@ -98,6 +98,7 @@ func (s *FiberServer) SetupServer(
|
||||
usageRepo := repositories.NewUsageRepository(redis)
|
||||
statisticRepo := repositories.NewStatisticRepository(poolPg, redis)
|
||||
chatRepo := repositories.NewChatRepository(poolPg, redis)
|
||||
battleReplayRepo := repositories.NewBattleReplayRepository(poolPg, redis)
|
||||
|
||||
// service setup
|
||||
authService := services.NewAuthService(userRepo, roleRepo, tokenRepo, redis, poolPg)
|
||||
@@ -115,10 +116,11 @@ func (s *FiberServer) SetupServer(
|
||||
submissionService := services.NewSubmissionService(
|
||||
submissionRepo, projectRepo, commitRepo,
|
||||
userRepo, wikiRepo, geometryRepo, entityRepo,
|
||||
raguRepo, raguUtils, poolPg, redis,
|
||||
battleReplayRepo, raguRepo, raguUtils, poolPg, redis,
|
||||
)
|
||||
chatbotService := services.NewChatbotService(raguRepo, usageRepo, chatRepo, raguUtils)
|
||||
statisticService := services.NewStatisticService(statisticRepo)
|
||||
battleReplayService := services.NewBattleReplayService(battleReplayRepo)
|
||||
|
||||
// controller setup
|
||||
authController := controllers.NewAuthController(authService, oauth)
|
||||
@@ -136,6 +138,7 @@ func (s *FiberServer) SetupServer(
|
||||
submissionController := controllers.NewSubmissionController(submissionService)
|
||||
chatbotController := controllers.NewChatbotController(chatbotService)
|
||||
statisticController := controllers.NewStatisticController(statisticService)
|
||||
battleReplayController := controllers.NewBattleReplayController(battleReplayService)
|
||||
|
||||
// route setup
|
||||
routes.AuthRoutes(s.App, authController, userRepo)
|
||||
@@ -152,5 +155,6 @@ func (s *FiberServer) SetupServer(
|
||||
routes.SubmissionRoutes(s.App, submissionController, userRepo)
|
||||
routes.ChatbotRoutes(s.App, chatbotController, userRepo)
|
||||
routes.StatisticRoutes(s.App, statisticController, userRepo)
|
||||
routes.BattleReplayRoutes(s.App, battleReplayController)
|
||||
routes.NotFoundRoute(s.App)
|
||||
}
|
||||
|
||||
1
db/migrations/000017_battle_replays.down.sql
Normal file
1
db/migrations/000017_battle_replays.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS battle_replays;
|
||||
28
db/migrations/000017_battle_replays.up.sql
Normal file
28
db/migrations/000017_battle_replays.up.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE IF NOT EXISTS battle_replays (
|
||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||
geometry_id UUID NOT NULL REFERENCES geometries(id) ON DELETE CASCADE,
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
target_geometry_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
detail JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_battle_replays_geometry_id ON battle_replays(geometry_id)
|
||||
WHERE is_deleted = false;
|
||||
|
||||
CREATE INDEX idx_battle_replays_project_id ON battle_replays(project_id)
|
||||
WHERE is_deleted = false;
|
||||
|
||||
CREATE INDEX idx_battle_replays_target_geometry_ids ON battle_replays USING GIN (target_geometry_ids)
|
||||
WHERE is_deleted = false;
|
||||
|
||||
CREATE INDEX idx_battle_replays_updated_at ON battle_replays(updated_at DESC)
|
||||
WHERE is_deleted = false;
|
||||
|
||||
DROP TRIGGER IF EXISTS trigger_battle_replays_updated_at ON battle_replays;
|
||||
CREATE TRIGGER trigger_battle_replays_updated_at
|
||||
BEFORE UPDATE ON battle_replays
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at();
|
||||
49
db/query/battle_replay.sql
Normal file
49
db/query/battle_replay.sql
Normal file
@@ -0,0 +1,49 @@
|
||||
-- name: CreateBattleReplay :one
|
||||
INSERT INTO battle_replays (
|
||||
id, geometry_id, project_id, target_geometry_ids, detail
|
||||
) VALUES (
|
||||
COALESCE(sqlc.narg('id')::uuid, uuidv7()), $1, $2, $3, $4
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetBattleReplayById :one
|
||||
SELECT *
|
||||
FROM battle_replays
|
||||
WHERE id = $1 AND is_deleted = false;
|
||||
|
||||
-- name: GetBattleReplaysByIDs :many
|
||||
SELECT * FROM battle_replays WHERE id = ANY($1::uuid[]) AND is_deleted = false;
|
||||
|
||||
-- name: GetBattleReplaysByGeometryId :many
|
||||
SELECT *
|
||||
FROM battle_replays
|
||||
WHERE geometry_id = $1 AND is_deleted = false;
|
||||
|
||||
-- name: GetBattleReplaysByGeometryIDs :many
|
||||
SELECT *
|
||||
FROM battle_replays
|
||||
WHERE geometry_id = ANY($1::uuid[]) AND is_deleted = false;
|
||||
|
||||
-- name: GetBattleReplaysByProjectId :many
|
||||
SELECT *
|
||||
FROM battle_replays
|
||||
WHERE project_id = $1 AND is_deleted = false;
|
||||
|
||||
-- name: UpdateBattleReplay :one
|
||||
UPDATE battle_replays
|
||||
SET
|
||||
geometry_id = COALESCE(sqlc.narg('geometry_id'), geometry_id),
|
||||
target_geometry_ids = COALESCE(sqlc.narg('target_geometry_ids'), target_geometry_ids),
|
||||
detail = COALESCE(sqlc.narg('detail'), detail)
|
||||
WHERE id = sqlc.arg('id') AND is_deleted = false
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteBattleReplay :exec
|
||||
UPDATE battle_replays
|
||||
SET is_deleted = true
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: DeleteBattleReplaysByIDs :exec
|
||||
UPDATE battle_replays
|
||||
SET is_deleted = true
|
||||
WHERE id = ANY($1::uuid[]);
|
||||
@@ -243,3 +243,14 @@ CREATE TABLE IF NOT EXISTS chatbot_histories (
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS battle_replays (
|
||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||
geometry_id UUID NOT NULL REFERENCES geometries(id) ON DELETE CASCADE,
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
target_geometry_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
detail JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
|
||||
109
docs/docs.go
109
docs/docs.go
@@ -399,6 +399,82 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/battle-replays/geometry/{geometryId}": {
|
||||
"get": {
|
||||
"description": "Get all battle replays associated with a specific geometry",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"BattleReplays"
|
||||
],
|
||||
"summary": "Get battle replays by geometry ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Geometry ID",
|
||||
"name": "geometryId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/battle-replays/{id}": {
|
||||
"get": {
|
||||
"description": "Get detailed information about a specific battle replay",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"BattleReplays"
|
||||
],
|
||||
"summary": "Get battle replay by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Battle Replay ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chatbot/chat": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -4241,6 +4317,33 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"history-api_internal_dtos_request.BattleReplaySnapshot": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"geometry_id",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"detail": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"geometry_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"target_geometry_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"history-api_internal_dtos_request.ChangeOwnerDto": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -4329,6 +4432,12 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/history-api_internal_dtos_request.GeometryEntitySnapshot"
|
||||
}
|
||||
},
|
||||
"replays": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_request.BattleReplaySnapshot"
|
||||
}
|
||||
},
|
||||
"wikis": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -392,6 +392,82 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/battle-replays/geometry/{geometryId}": {
|
||||
"get": {
|
||||
"description": "Get all battle replays associated with a specific geometry",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"BattleReplays"
|
||||
],
|
||||
"summary": "Get battle replays by geometry ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Geometry ID",
|
||||
"name": "geometryId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/battle-replays/{id}": {
|
||||
"get": {
|
||||
"description": "Get detailed information about a specific battle replay",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"BattleReplays"
|
||||
],
|
||||
"summary": "Get battle replay by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Battle Replay ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_response.CommonResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chatbot/chat": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -4234,6 +4310,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"history-api_internal_dtos_request.BattleReplaySnapshot": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"geometry_id",
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"detail": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"geometry_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"target_geometry_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"history-api_internal_dtos_request.ChangeOwnerDto": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -4322,6 +4425,12 @@
|
||||
"$ref": "#/definitions/history-api_internal_dtos_request.GeometryEntitySnapshot"
|
||||
}
|
||||
},
|
||||
"replays": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/history-api_internal_dtos_request.BattleReplaySnapshot"
|
||||
}
|
||||
},
|
||||
"wikis": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -29,6 +29,24 @@ definitions:
|
||||
- min_lat
|
||||
- min_lng
|
||||
type: object
|
||||
history-api_internal_dtos_request.BattleReplaySnapshot:
|
||||
properties:
|
||||
detail:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
geometry_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
target_geometry_ids:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- geometry_id
|
||||
- id
|
||||
type: object
|
||||
history-api_internal_dtos_request.ChangeOwnerDto:
|
||||
properties:
|
||||
new_owner_id:
|
||||
@@ -88,6 +106,10 @@ definitions:
|
||||
items:
|
||||
$ref: '#/definitions/history-api_internal_dtos_request.GeometryEntitySnapshot'
|
||||
type: array
|
||||
replays:
|
||||
items:
|
||||
$ref: '#/definitions/history-api_internal_dtos_request.BattleReplaySnapshot'
|
||||
type: array
|
||||
wikis:
|
||||
items:
|
||||
$ref: '#/definitions/history-api_internal_dtos_request.WikiSnapshot'
|
||||
@@ -947,6 +969,56 @@ paths:
|
||||
summary: Verify a security token
|
||||
tags:
|
||||
- Auth
|
||||
/battle-replays/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get detailed information about a specific battle replay
|
||||
parameters:
|
||||
- description: Battle Replay 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'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||
summary: Get battle replay by ID
|
||||
tags:
|
||||
- BattleReplays
|
||||
/battle-replays/geometry/{geometryId}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get all battle replays associated with a specific geometry
|
||||
parameters:
|
||||
- description: Geometry ID
|
||||
in: path
|
||||
name: geometryId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/history-api_internal_dtos_response.CommonResponse'
|
||||
summary: Get battle replays by geometry ID
|
||||
tags:
|
||||
- BattleReplays
|
||||
/chatbot/chat:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
72
internal/controllers/battleReplayController.go
Normal file
72
internal/controllers/battleReplayController.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"history-api/internal/dtos/response"
|
||||
"history-api/internal/services"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
type BattleReplayController struct {
|
||||
service services.BattleReplayService
|
||||
}
|
||||
|
||||
func NewBattleReplayController(svc services.BattleReplayService) *BattleReplayController {
|
||||
return &BattleReplayController{service: svc}
|
||||
}
|
||||
|
||||
// GetBattleReplayById handles fetching a single battle replay by ID.
|
||||
// @Summary Get battle replay by ID
|
||||
// @Description Get detailed information about a specific battle replay
|
||||
// @Tags BattleReplays
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Battle Replay ID"
|
||||
// @Success 200 {object} response.CommonResponse
|
||||
// @Failure 404 {object} response.CommonResponse
|
||||
// @Router /battle-replays/{id} [get]
|
||||
func (h *BattleReplayController) GetBattleReplayById(c fiber.Ctx) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
id := c.Params("id")
|
||||
res, err := h.service.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return c.Status(err.Code).JSON(response.CommonResponse{
|
||||
Status: false,
|
||||
Message: err.Message,
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: res,
|
||||
})
|
||||
}
|
||||
|
||||
// GetBattleReplaysByGeometryId handles fetching battle replays by geometry ID.
|
||||
// @Summary Get battle replays by geometry ID
|
||||
// @Description Get all battle replays associated with a specific geometry
|
||||
// @Tags BattleReplays
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param geometryId path string true "Geometry ID"
|
||||
// @Success 200 {object} response.CommonResponse
|
||||
// @Failure 400 {object} response.CommonResponse
|
||||
// @Router /battle-replays/geometry/{geometryId} [get]
|
||||
func (h *BattleReplayController) GetBattleReplaysByGeometryId(c fiber.Ctx) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
geometryID := c.Params("geometryId")
|
||||
res, err := h.service.GetByGeometryID(ctx, geometryID)
|
||||
if err != nil {
|
||||
return c.Status(err.Code).JSON(response.CommonResponse{
|
||||
Status: false,
|
||||
Message: err.Message,
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: res,
|
||||
})
|
||||
}
|
||||
@@ -9,6 +9,14 @@ type CommitSnapshot struct {
|
||||
Wikis []*WikiSnapshot `json:"wikis,omitempty" validate:"omitempty,dive"`
|
||||
GeometryEntity []*GeometryEntitySnapshot `json:"geometry_entity,omitempty" validate:"omitempty,dive"`
|
||||
EntityWiki []*EntityWikiLinkSnapshot `json:"entity_wiki,omitempty" validate:"omitempty,dive"`
|
||||
Replays []*BattleReplaySnapshot `json:"replays,omitempty" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
type BattleReplaySnapshot struct {
|
||||
ID string `json:"id" validate:"required,uuidv7"`
|
||||
GeometryID string `json:"geometry_id" validate:"required,uuidv7"`
|
||||
TargetGeometryIDs []string `json:"target_geometry_ids,omitempty" validate:"omitempty,dive,uuidv7"`
|
||||
Detail json.RawMessage `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
type FeatureCollection struct {
|
||||
|
||||
17
internal/dtos/response/battle_replay.go
Normal file
17
internal/dtos/response/battle_replay.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BattleReplayResponse struct {
|
||||
ID string `json:"id"`
|
||||
GeometryID string `json:"geometry_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
TargetGeometryIDs json.RawMessage `json:"target_geometry_ids,omitempty"`
|
||||
Detail json.RawMessage `json:"detail,omitempty"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
272
internal/gen/sqlc/battle_replay.sql.go
Normal file
272
internal/gen/sqlc/battle_replay.sql.go
Normal file
@@ -0,0 +1,272 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: battle_replay.sql
|
||||
|
||||
package sqlc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const createBattleReplay = `-- name: CreateBattleReplay :one
|
||||
INSERT INTO battle_replays (
|
||||
id, geometry_id, project_id, target_geometry_ids, detail
|
||||
) VALUES (
|
||||
COALESCE($5::uuid, uuidv7()), $1, $2, $3, $4
|
||||
)
|
||||
RETURNING id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateBattleReplayParams struct {
|
||||
GeometryID pgtype.UUID `json:"geometry_id"`
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
TargetGeometryIds json.RawMessage `json:"target_geometry_ids"`
|
||||
Detail json.RawMessage `json:"detail"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateBattleReplay(ctx context.Context, arg CreateBattleReplayParams) (BattleReplay, error) {
|
||||
row := q.db.QueryRow(ctx, createBattleReplay,
|
||||
arg.GeometryID,
|
||||
arg.ProjectID,
|
||||
arg.TargetGeometryIds,
|
||||
arg.Detail,
|
||||
arg.ID,
|
||||
)
|
||||
var i BattleReplay
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteBattleReplay = `-- name: DeleteBattleReplay :exec
|
||||
UPDATE battle_replays
|
||||
SET is_deleted = true
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteBattleReplay(ctx context.Context, id pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteBattleReplay, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteBattleReplaysByIDs = `-- name: DeleteBattleReplaysByIDs :exec
|
||||
UPDATE battle_replays
|
||||
SET is_deleted = true
|
||||
WHERE id = ANY($1::uuid[])
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteBattleReplaysByIDs(ctx context.Context, dollar_1 []pgtype.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteBattleReplaysByIDs, dollar_1)
|
||||
return err
|
||||
}
|
||||
|
||||
const getBattleReplayById = `-- name: GetBattleReplayById :one
|
||||
SELECT id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at
|
||||
FROM battle_replays
|
||||
WHERE id = $1 AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetBattleReplayById(ctx context.Context, id pgtype.UUID) (BattleReplay, error) {
|
||||
row := q.db.QueryRow(ctx, getBattleReplayById, id)
|
||||
var i BattleReplay
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getBattleReplaysByGeometryIDs = `-- name: GetBattleReplaysByGeometryIDs :many
|
||||
SELECT id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at
|
||||
FROM battle_replays
|
||||
WHERE geometry_id = ANY($1::uuid[]) AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetBattleReplaysByGeometryIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]BattleReplay, error) {
|
||||
rows, err := q.db.Query(ctx, getBattleReplaysByGeometryIDs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []BattleReplay{}
|
||||
for rows.Next() {
|
||||
var i BattleReplay
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&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 getBattleReplaysByGeometryId = `-- name: GetBattleReplaysByGeometryId :many
|
||||
SELECT id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at
|
||||
FROM battle_replays
|
||||
WHERE geometry_id = $1 AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetBattleReplaysByGeometryId(ctx context.Context, geometryID pgtype.UUID) ([]BattleReplay, error) {
|
||||
rows, err := q.db.Query(ctx, getBattleReplaysByGeometryId, geometryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []BattleReplay{}
|
||||
for rows.Next() {
|
||||
var i BattleReplay
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&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 getBattleReplaysByIDs = `-- name: GetBattleReplaysByIDs :many
|
||||
SELECT id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at FROM battle_replays WHERE id = ANY($1::uuid[]) AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetBattleReplaysByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]BattleReplay, error) {
|
||||
rows, err := q.db.Query(ctx, getBattleReplaysByIDs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []BattleReplay{}
|
||||
for rows.Next() {
|
||||
var i BattleReplay
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&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 getBattleReplaysByProjectId = `-- name: GetBattleReplaysByProjectId :many
|
||||
SELECT id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at
|
||||
FROM battle_replays
|
||||
WHERE project_id = $1 AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetBattleReplaysByProjectId(ctx context.Context, projectID pgtype.UUID) ([]BattleReplay, error) {
|
||||
rows, err := q.db.Query(ctx, getBattleReplaysByProjectId, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []BattleReplay{}
|
||||
for rows.Next() {
|
||||
var i BattleReplay
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&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 updateBattleReplay = `-- name: UpdateBattleReplay :one
|
||||
UPDATE battle_replays
|
||||
SET
|
||||
geometry_id = COALESCE($1, geometry_id),
|
||||
target_geometry_ids = COALESCE($2, target_geometry_ids),
|
||||
detail = COALESCE($3, detail)
|
||||
WHERE id = $4 AND is_deleted = false
|
||||
RETURNING id, geometry_id, project_id, target_geometry_ids, detail, is_deleted, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateBattleReplayParams struct {
|
||||
GeometryID pgtype.UUID `json:"geometry_id"`
|
||||
TargetGeometryIds []byte `json:"target_geometry_ids"`
|
||||
Detail []byte `json:"detail"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBattleReplay(ctx context.Context, arg UpdateBattleReplayParams) (BattleReplay, error) {
|
||||
row := q.db.QueryRow(ctx, updateBattleReplay,
|
||||
arg.GeometryID,
|
||||
arg.TargetGeometryIds,
|
||||
arg.Detail,
|
||||
arg.ID,
|
||||
)
|
||||
var i BattleReplay
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.GeometryID,
|
||||
&i.ProjectID,
|
||||
&i.TargetGeometryIds,
|
||||
&i.Detail,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -11,6 +11,17 @@ import (
|
||||
"github.com/pgvector/pgvector-go"
|
||||
)
|
||||
|
||||
type BattleReplay struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
GeometryID pgtype.UUID `json:"geometry_id"`
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
TargetGeometryIds json.RawMessage `json:"target_geometry_ids"`
|
||||
Detail json.RawMessage `json:"detail"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ChatbotHistory struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
UserID pgtype.UUID `json:"user_id"`
|
||||
|
||||
48
internal/models/battle_replay.go
Normal file
48
internal/models/battle_replay.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"history-api/internal/dtos/response"
|
||||
"time"
|
||||
)
|
||||
|
||||
type BattleReplayEntity struct {
|
||||
ID string `json:"id"`
|
||||
GeometryID string `json:"geometry_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
TargetGeometryIDs json.RawMessage `json:"target_geometry_ids"`
|
||||
Detail json.RawMessage `json:"detail"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (b *BattleReplayEntity) ToResponse() *response.BattleReplayResponse {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return &response.BattleReplayResponse{
|
||||
ID: b.ID,
|
||||
GeometryID: b.GeometryID,
|
||||
ProjectID: b.ProjectID,
|
||||
TargetGeometryIDs: b.TargetGeometryIDs,
|
||||
Detail: b.Detail,
|
||||
IsDeleted: b.IsDeleted,
|
||||
CreatedAt: b.CreatedAt,
|
||||
UpdatedAt: b.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func BattleReplaysEntityToResponse(bs []*BattleReplayEntity) []*response.BattleReplayResponse {
|
||||
out := make([]*response.BattleReplayResponse, 0)
|
||||
if bs == nil {
|
||||
return out
|
||||
}
|
||||
for _, b := range bs {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, b.ToResponse())
|
||||
}
|
||||
return out
|
||||
}
|
||||
290
internal/repositories/battleReplayRepository.go
Normal file
290
internal/repositories/battleReplayRepository.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"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 BattleReplayRepository interface {
|
||||
GetByID(ctx context.Context, id pgtype.UUID) (*models.BattleReplayEntity, error)
|
||||
GetByIDs(ctx context.Context, ids []string) ([]*models.BattleReplayEntity, error)
|
||||
GetByGeometryID(ctx context.Context, geometryID pgtype.UUID) ([]*models.BattleReplayEntity, error)
|
||||
GetByGeometryIDs(ctx context.Context, geometryIDs []string) ([]*models.BattleReplayEntity, error)
|
||||
Create(ctx context.Context, params sqlc.CreateBattleReplayParams) (*models.BattleReplayEntity, error)
|
||||
Update(ctx context.Context, params sqlc.UpdateBattleReplayParams) (*models.BattleReplayEntity, error)
|
||||
Delete(ctx context.Context, id pgtype.UUID) error
|
||||
DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error
|
||||
GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.BattleReplayEntity, error)
|
||||
WithTx(tx pgx.Tx) BattleReplayRepository
|
||||
}
|
||||
|
||||
type battleReplayRepository struct {
|
||||
q *sqlc.Queries
|
||||
c cache.Cache
|
||||
}
|
||||
|
||||
func NewBattleReplayRepository(db sqlc.DBTX, c cache.Cache) BattleReplayRepository {
|
||||
return &battleReplayRepository{
|
||||
q: sqlc.New(db),
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) WithTx(tx pgx.Tx) BattleReplayRepository {
|
||||
return &battleReplayRepository{
|
||||
q: r.q.WithTx(tx),
|
||||
c: r.c,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) rowToEntity(row sqlc.BattleReplay) *models.BattleReplayEntity {
|
||||
return &models.BattleReplayEntity{
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
GeometryID: convert.UUIDToString(row.GeometryID),
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
TargetGeometryIDs: row.TargetGeometryIds,
|
||||
Detail: row.Detail,
|
||||
IsDeleted: row.IsDeleted,
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.BattleReplayEntity, error) {
|
||||
if len(ids) == 0 {
|
||||
return []*models.BattleReplayEntity{}, nil
|
||||
}
|
||||
keys := make([]string, len(ids))
|
||||
for i, id := range ids {
|
||||
keys[i] = fmt.Sprintf("battle_replay:id:%s", id)
|
||||
}
|
||||
raws := r.c.MGet(ctx, keys...)
|
||||
|
||||
var items []*models.BattleReplayEntity
|
||||
missingToCache := make(map[string]any)
|
||||
|
||||
var missingPgIds []pgtype.UUID
|
||||
for i, b := range raws {
|
||||
if len(b) == 0 {
|
||||
pgId := pgtype.UUID{}
|
||||
err := pgId.Scan(ids[i])
|
||||
if err == nil {
|
||||
missingPgIds = append(missingPgIds, pgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbMap := make(map[string]*models.BattleReplayEntity)
|
||||
if len(missingPgIds) > 0 {
|
||||
dbRows, err := r.q.GetBattleReplaysByIDs(ctx, missingPgIds)
|
||||
if err == nil {
|
||||
for _, row := range dbRows {
|
||||
item := r.rowToEntity(row)
|
||||
dbMap[item.ID] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range raws {
|
||||
if len(b) > 0 {
|
||||
var u models.BattleReplayEntity
|
||||
if err := json.Unmarshal(b, &u); err == nil {
|
||||
items = append(items, &u)
|
||||
}
|
||||
} else {
|
||||
if item, ok := dbMap[ids[i]]; ok {
|
||||
items = append(items, item)
|
||||
missingToCache[keys[i]] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) GetByIDs(ctx context.Context, ids []string) ([]*models.BattleReplayEntity, error) {
|
||||
return r.getByIDsWithFallback(ctx, ids)
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.BattleReplayEntity, error) {
|
||||
cacheId := fmt.Sprintf("battle_replay:id:%s", convert.UUIDToString(id))
|
||||
var item models.BattleReplayEntity
|
||||
err := r.c.Get(ctx, cacheId, &item)
|
||||
if err == nil {
|
||||
_ = r.c.Set(ctx, cacheId, item, constants.NormalCacheDuration)
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
row, err := r.q.GetBattleReplayById(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entity := r.rowToEntity(row)
|
||||
_ = r.c.Set(ctx, cacheId, entity, constants.NormalCacheDuration)
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) GetByGeometryID(ctx context.Context, geometryID pgtype.UUID) ([]*models.BattleReplayEntity, error) {
|
||||
cacheKey := fmt.Sprintf("battle_replay:geometry:%s", convert.UUIDToString(geometryID))
|
||||
var cachedIDs []string
|
||||
if err := r.c.Get(ctx, cacheKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||
return r.getByIDsWithFallback(ctx, cachedIDs)
|
||||
}
|
||||
|
||||
rows, err := r.q.GetBattleReplaysByGeometryId(ctx, geometryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []*models.BattleReplayEntity
|
||||
var ids []string
|
||||
itemToCache := make(map[string]any)
|
||||
|
||||
for _, row := range rows {
|
||||
item := r.rowToEntity(row)
|
||||
ids = append(ids, item.ID)
|
||||
items = append(items, item)
|
||||
itemToCache[fmt.Sprintf("battle_replay:id:%s", item.ID)] = item
|
||||
}
|
||||
|
||||
if len(itemToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, itemToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
_ = r.c.Set(ctx, cacheKey, ids, constants.ListCacheDuration)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) GetByGeometryIDs(ctx context.Context, geometryIDs []string) ([]*models.BattleReplayEntity, error) {
|
||||
if len(geometryIDs) == 0 {
|
||||
return []*models.BattleReplayEntity{}, nil
|
||||
}
|
||||
|
||||
var pgIds []pgtype.UUID
|
||||
for _, id := range geometryIDs {
|
||||
pgId := pgtype.UUID{}
|
||||
if err := pgId.Scan(id); err == nil {
|
||||
pgIds = append(pgIds, pgId)
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := r.q.GetBattleReplaysByGeometryIDs(ctx, pgIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []*models.BattleReplayEntity
|
||||
itemToCache := make(map[string]any)
|
||||
|
||||
for _, row := range rows {
|
||||
item := r.rowToEntity(row)
|
||||
items = append(items, item)
|
||||
itemToCache[fmt.Sprintf("battle_replay:id:%s", item.ID)] = item
|
||||
}
|
||||
|
||||
if len(itemToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, itemToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.BattleReplayEntity, error) {
|
||||
cacheKey := fmt.Sprintf("battle_replay:project:%s", convert.UUIDToString(projectID))
|
||||
var cachedIDs []string
|
||||
if err := r.c.Get(ctx, cacheKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||
return r.getByIDsWithFallback(ctx, cachedIDs)
|
||||
}
|
||||
|
||||
rows, err := r.q.GetBattleReplaysByProjectId(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []*models.BattleReplayEntity
|
||||
var ids []string
|
||||
itemToCache := make(map[string]any)
|
||||
|
||||
for _, row := range rows {
|
||||
item := r.rowToEntity(row)
|
||||
ids = append(ids, item.ID)
|
||||
items = append(items, item)
|
||||
itemToCache[fmt.Sprintf("battle_replay:id:%s", item.ID)] = item
|
||||
}
|
||||
|
||||
if len(itemToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, itemToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
_ = r.c.Set(ctx, cacheKey, ids, constants.ListCacheDuration)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) Create(ctx context.Context, params sqlc.CreateBattleReplayParams) (*models.BattleReplayEntity, error) {
|
||||
row, err := r.q.CreateBattleReplay(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entity := r.rowToEntity(row)
|
||||
|
||||
_ = r.c.Del(ctx, fmt.Sprintf("battle_replay:project:%s", entity.ProjectID))
|
||||
_ = r.c.Del(ctx, fmt.Sprintf("battle_replay:geometry:%s", entity.GeometryID))
|
||||
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) Update(ctx context.Context, params sqlc.UpdateBattleReplayParams) (*models.BattleReplayEntity, error) {
|
||||
row, err := r.q.UpdateBattleReplay(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entity := r.rowToEntity(row)
|
||||
_ = r.c.Del(ctx, fmt.Sprintf("battle_replay:id:%s", entity.ID))
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
||||
err := r.q.DeleteBattleReplay(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = r.c.Del(ctx, fmt.Sprintf("battle_replay:id:%s", convert.UUIDToString(id)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *battleReplayRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) error {
|
||||
err := r.q.DeleteBattleReplaysByIDs(ctx, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
keys := make([]string, len(ids))
|
||||
for i, id := range ids {
|
||||
keys[i] = fmt.Sprintf("battle_replay:id:%s", convert.UUIDToString(id))
|
||||
}
|
||||
_ = r.c.Del(ctx, keys...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
13
internal/routes/battleReplayRoute.go
Normal file
13
internal/routes/battleReplayRoute.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"history-api/internal/controllers"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func BattleReplayRoutes(router fiber.Router, battleReplayController *controllers.BattleReplayController) {
|
||||
br := router.Group("/battle-replays")
|
||||
br.Get("/geometry/:geometryId", battleReplayController.GetBattleReplaysByGeometryId)
|
||||
br.Get("/:id", battleReplayController.GetBattleReplayById)
|
||||
}
|
||||
54
internal/services/battleReplayService.go
Normal file
54
internal/services/battleReplayService.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"history-api/internal/dtos/response"
|
||||
"history-api/internal/models"
|
||||
"history-api/internal/repositories"
|
||||
"history-api/pkg/convert"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
type BattleReplayService interface {
|
||||
GetByID(ctx context.Context, id string) (*response.BattleReplayResponse, *fiber.Error)
|
||||
GetByGeometryID(ctx context.Context, geometryID string) ([]*response.BattleReplayResponse, *fiber.Error)
|
||||
}
|
||||
|
||||
type battleReplayService struct {
|
||||
battleReplayRepo repositories.BattleReplayRepository
|
||||
}
|
||||
|
||||
func NewBattleReplayService(battleReplayRepo repositories.BattleReplayRepository) BattleReplayService {
|
||||
return &battleReplayService{
|
||||
battleReplayRepo: battleReplayRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *battleReplayService) GetByID(ctx context.Context, id string) (*response.BattleReplayResponse, *fiber.Error) {
|
||||
replayUUID, err := convert.StringToUUID(id)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid battle replay ID format")
|
||||
}
|
||||
|
||||
replay, err := s.battleReplayRepo.GetByID(ctx, replayUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Battle replay not found")
|
||||
}
|
||||
|
||||
return replay.ToResponse(), nil
|
||||
}
|
||||
|
||||
func (s *battleReplayService) GetByGeometryID(ctx context.Context, geometryID string) ([]*response.BattleReplayResponse, *fiber.Error) {
|
||||
geomUUID, err := convert.StringToUUID(geometryID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid geometry ID format")
|
||||
}
|
||||
|
||||
replays, err := s.battleReplayRepo.GetByGeometryID(ctx, geomUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get battle replays")
|
||||
}
|
||||
|
||||
return models.BattleReplaysEntityToResponse(replays), nil
|
||||
}
|
||||
@@ -39,6 +39,7 @@ type submissionService struct {
|
||||
wikiRepo repositories.WikiRepository
|
||||
geometryRepo repositories.GeometryRepository
|
||||
entityRepo repositories.EntityRepository
|
||||
battleReplayRepo repositories.BattleReplayRepository
|
||||
ragRepo repositories.RagRepository
|
||||
ragUtils *ai.RagUtils
|
||||
db *pgxpool.Pool
|
||||
@@ -53,6 +54,7 @@ func NewSubmissionService(
|
||||
wikiRepo repositories.WikiRepository,
|
||||
geometryRepo repositories.GeometryRepository,
|
||||
entityRepo repositories.EntityRepository,
|
||||
battleReplayRepo repositories.BattleReplayRepository,
|
||||
ragRepo repositories.RagRepository,
|
||||
ragUtils *ai.RagUtils,
|
||||
db *pgxpool.Pool,
|
||||
@@ -66,6 +68,7 @@ func NewSubmissionService(
|
||||
wikiRepo: wikiRepo,
|
||||
geometryRepo: geometryRepo,
|
||||
entityRepo: entityRepo,
|
||||
battleReplayRepo: battleReplayRepo,
|
||||
ragRepo: ragRepo,
|
||||
ragUtils: ragUtils,
|
||||
db: db,
|
||||
@@ -187,6 +190,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
entityRepo := s.entityRepo.WithTx(tx)
|
||||
geometryRepo := s.geometryRepo.WithTx(tx)
|
||||
wikiRepo := s.wikiRepo.WithTx(tx)
|
||||
battleReplayRepo := s.battleReplayRepo.WithTx(tx)
|
||||
|
||||
submissionUUID, err := convert.StringToUUID(submissionID)
|
||||
if err != nil {
|
||||
@@ -229,6 +233,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
listDeleteEntities := make([]pgtype.UUID, 0)
|
||||
listDeleteWikis := make([]pgtype.UUID, 0)
|
||||
listDeleteGeometries := make([]pgtype.UUID, 0)
|
||||
listDeleteBattleReplays := make([]pgtype.UUID, 0)
|
||||
var snapshotData request.CommitSnapshot
|
||||
err = json.Unmarshal(commit.SnapshotJson, &snapshotData)
|
||||
if err != nil {
|
||||
@@ -242,17 +247,22 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
}
|
||||
currentEntity, err := s.entityRepo.GetByProjectID(ctx, projectUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Entity not found")
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Entity not found: "+err.Error())
|
||||
}
|
||||
|
||||
currentGeometry, err := s.geometryRepo.GetByProjectID(ctx, projectUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Geometry not found")
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Geometry not found: "+err.Error())
|
||||
}
|
||||
|
||||
currentWiki, err := s.wikiRepo.GetByProjectID(ctx, projectUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Wiki not found")
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Wiki not found: "+err.Error())
|
||||
}
|
||||
|
||||
currentBattleReplay, err := s.battleReplayRepo.GetByProjectID(ctx, projectUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Battle replay not found: "+err.Error())
|
||||
}
|
||||
|
||||
persistItemIDs := make(map[string]struct{})
|
||||
@@ -265,6 +275,9 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
for _, item := range snapshotData.Wikis {
|
||||
persistItemIDs[item.ID] = struct{}{}
|
||||
}
|
||||
for _, item := range snapshotData.Replays {
|
||||
persistItemIDs[item.ID] = struct{}{}
|
||||
}
|
||||
|
||||
persistCurrentItemIDs := make(map[string]struct{})
|
||||
for _, item := range currentEntity {
|
||||
@@ -276,6 +289,9 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
for _, item := range currentWiki {
|
||||
persistCurrentItemIDs[item.ID] = struct{}{}
|
||||
}
|
||||
for _, item := range currentBattleReplay {
|
||||
persistCurrentItemIDs[item.ID] = struct{}{}
|
||||
}
|
||||
|
||||
for _, e := range currentEntity {
|
||||
if _, ok := persistItemIDs[e.ID]; !ok {
|
||||
@@ -310,6 +326,17 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
}
|
||||
}
|
||||
|
||||
for _, br := range currentBattleReplay {
|
||||
if _, ok := persistItemIDs[br.ID]; !ok {
|
||||
itemUUID, err := convert.StringToUUID(br.ID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid battle replay ID")
|
||||
}
|
||||
listDeleteBattleReplays = append(listDeleteBattleReplays, itemUUID)
|
||||
delete(persistCurrentItemIDs, br.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(listDeleteEntities) > 0 {
|
||||
if err = entityRepo.DeleteByIDs(ctx, listDeleteEntities); err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete entities")
|
||||
@@ -328,6 +355,12 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
}
|
||||
}
|
||||
|
||||
if len(listDeleteBattleReplays) > 0 {
|
||||
if err = battleReplayRepo.DeleteByIDs(ctx, listDeleteBattleReplays); err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete battle replays")
|
||||
}
|
||||
}
|
||||
|
||||
refEntityIDs := []string{}
|
||||
for _, e := range snapshotData.Entities {
|
||||
if e.Source == "ref" {
|
||||
@@ -570,6 +603,46 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
}
|
||||
snapshotData.Wikis = newWikis
|
||||
|
||||
for _, replay := range snapshotData.Replays {
|
||||
replayUUID, err := convert.StringToUUID(replay.ID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid battle replay ID")
|
||||
}
|
||||
|
||||
geomUUID, err := convert.StringToUUID(replay.GeometryID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID in battle replay")
|
||||
}
|
||||
|
||||
targetIDs, err := json.Marshal(replay.TargetGeometryIDs)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal target geometry IDs")
|
||||
}
|
||||
|
||||
if _, ok := persistCurrentItemIDs[replay.ID]; ok {
|
||||
_, err := battleReplayRepo.Update(ctx, sqlc.UpdateBattleReplayParams{
|
||||
ID: replayUUID,
|
||||
GeometryID: geomUUID,
|
||||
TargetGeometryIds: targetIDs,
|
||||
Detail: replay.Detail,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update battle replay: "+err.Error())
|
||||
}
|
||||
} else {
|
||||
_, err := battleReplayRepo.Create(ctx, sqlc.CreateBattleReplayParams{
|
||||
ID: replayUUID,
|
||||
GeometryID: geomUUID,
|
||||
ProjectID: projectUUID,
|
||||
TargetGeometryIds: targetIDs,
|
||||
Detail: replay.Detail,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create battle replay: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = geometryRepo.DeleteEntityGeometriesByProjectID(ctx, projectUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete geometry entity: "+err.Error())
|
||||
|
||||
Reference in New Issue
Block a user