diff --git a/internal/chronicle/adapter/http/game/game_request.go b/internal/chronicle/adapter/http/game/game_request.go index 6ee343d..2a4830e 100644 --- a/internal/chronicle/adapter/http/game/game_request.go +++ b/internal/chronicle/adapter/http/game/game_request.go @@ -14,13 +14,6 @@ type GameRequest struct { Name string `jsonapi:"attr,name"` Type string `jsonapi:"attr,type"` Worlds []*WorldRequest `jsonapi:"relation,worlds"` - // ID int `jsonapi:"primary,blogs"` - // Title string `jsonapi:"attr,title"` - // Posts []*Post `jsonapi:"relation,posts"` - // CurrentPost *Post `jsonapi:"relation,current_post"` - // CurrentPostID int `jsonapi:"attr,current_post_id"` - // CreatedAt time.Time `jsonapi:"attr,created_at"` - // ViewCount int `jsonapi:"attr,view_count"` } func (a *GameRequest) Bind(r *http.Request) error { diff --git a/internal/chronicle/adapter/http/game/game_server.go b/internal/chronicle/adapter/http/game/game_server.go deleted file mode 100644 index b35d149..0000000 --- a/internal/chronicle/adapter/http/game/game_server.go +++ /dev/null @@ -1,244 +0,0 @@ -package game - -import ( - "net/http" - - corePort "github.com/SomethingSexy/chronicle/internal/chronicle/core/port" - "github.com/SomethingSexy/chronicle/internal/chronicle/port" - "github.com/SomethingSexy/chronicle/internal/common" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" - "github.com/google/jsonapi" - "github.com/google/uuid" -) - -func NewGameHttpServer(commands port.ChronicleCommands, queries port.GameQueries) GameHttpServer { - return GameHttpServer{ - commands: commands, - queries: queries, - } -} - -type GameHttpServer struct { - commands port.ChronicleCommands - queries port.GameQueries -} - -func (h GameHttpServer) Routes() chi.Router { - r := chi.NewRouter() - r.Post("/", h.CreateGame) - r.Get("/", h.ListGames) - r.Get("/{gameId}", h.GetGame) - - // TODO: This is obviously going to get huge - // we need to decide how to organize these route handlers - // Either at this level or a higher level to apply - // relationships to root routes. - r.Post("/{gameId}/worlds", h.CreateWorld) - r.Get("/{gameId}/worlds/{worldId}", h.GetWorld) - r.Post("/{gameId}/worlds/{worldId}/locations", h.CreateLocation) - r.Get("/{gameId}/worlds/{worldId}/locations", h.GetLocations) - - return r -} - -func (h GameHttpServer) CreateGame(w http.ResponseWriter, r *http.Request) { - data := &GameRequest{} - if err := render.Bind(r, data); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - if err := h.commands.CreateGame.Handle(r.Context(), corePort.CreateGame{ - Game: data.ToDomain(), - }); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - render.Status(r, http.StatusCreated) -} - -func (h GameHttpServer) ListGames(w http.ResponseWriter, r *http.Request) { - games, err := h.queries.ListGames.Handle(r.Context(), corePort.AllGamesQuery{}) - if err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - responses := make([]*GameRequest, len(games)) - for i, game := range games { - worlds := make([]*WorldRequest, len(game.Worlds)) - - for x, world := range game.Worlds { - worlds[x] = &WorldRequest{ - ID: world.WorldId.String(), - WorldId: world.WorldId.String(), - GameId: world.GameId.String(), - Name: world.Name, - } - } - responses[i] = &GameRequest{ - ID: game.GameId.String(), - GameId: game.GameId.String(), - Name: game.Name, - Type: game.Type, - Worlds: worlds, - } - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", jsonapi.MediaType) - if err := jsonapi.MarshalPayload(w, responses); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } -} - -func (h GameHttpServer) GetGame(w http.ResponseWriter, r *http.Request) { - gameId := chi.URLParam(r, "gameId") - - game, err := h.queries.GetGame.Handle(r.Context(), corePort.GetGameQuery{ - // TODO: BAD check for error - GameId: uuid.MustParse(gameId), - }) - if err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - response := &GameRequest{ - ID: game.GameId.String(), - GameId: game.GameId.String(), - Name: game.Name, - Type: game.Type, - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", jsonapi.MediaType) - if err := jsonapi.MarshalPayload(w, response); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } -} - -func (h GameHttpServer) CreateWorld(w http.ResponseWriter, r *http.Request) { - data := &WorldRequest{} - if err := render.Bind(r, data); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - if err := h.commands.CreateWorld.Handle(r.Context(), corePort.CreateWorld{ - World: data.ToDomain(), - }); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - render.Status(r, http.StatusCreated) -} - -func (h GameHttpServer) GetWorld(w http.ResponseWriter, r *http.Request) { - gameId := chi.URLParam(r, "gameId") - worldId := chi.URLParam(r, "worldId") - - world, err := h.queries.GetWorld.Handle(r.Context(), corePort.GetWorldQuery{ - // TODO: BAD check for error - GameId: uuid.MustParse(gameId), - WorldId: uuid.MustParse(worldId), - }) - if err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - locations := make([]*LocationRequest, len(world.Locations)) - for i, location := range world.Locations { - paths := make([]string, len(location.Path)) - for x, path := range location.Path { - paths[x] = path.String() - } - - locations[i] = &LocationRequest{ - ID: location.LocationId.String(), - LocationId: location.LocationId.String(), - WorldId: location.WorldId.String(), - Name: location.Name, - Type: location.Type, - Path: paths, - } - } - - response := &WorldRequest{ - ID: world.WorldId.String(), - WorldId: world.WorldId.String(), - GameId: world.GameId.String(), - Name: world.Name, - Locations: locations, - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", jsonapi.MediaType) - if err := jsonapi.MarshalPayload(w, response); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } -} - -func (h GameHttpServer) CreateLocation(w http.ResponseWriter, r *http.Request) { - data := &LocationRequest{} - if err := render.Bind(r, data); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - if err := h.commands.CreateLocation.Handle(r.Context(), corePort.CreateLocation{ - Location: data.ToDomain(), - }); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - render.Status(r, http.StatusCreated) -} - -func (h GameHttpServer) GetLocations(w http.ResponseWriter, r *http.Request) { - gameId := chi.URLParam(r, "gameId") - worldId := chi.URLParam(r, "worldId") - - locations, err := h.queries.ListLocations.Handle(r.Context(), corePort.LocationsQuery{ - // TODO: BAD check for error - GameId: uuid.MustParse(gameId), - WorldId: uuid.MustParse(worldId), - }) - if err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } - - responses := make([]*LocationRequest, len(locations)) - - for i, location := range locations { - paths := make([]string, len(location.Path)) - for x, path := range location.Path { - paths[x] = path.String() - } - - responses[i] = &LocationRequest{ - ID: location.LocationId.String(), - LocationId: location.LocationId.String(), - WorldId: location.WorldId.String(), - Name: location.Name, - Type: location.Type, - Path: paths, - } - } - - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", jsonapi.MediaType) - if err := jsonapi.MarshalPayload(w, responses); err != nil { - render.Render(w, r, common.ErrInvalidRequest(err)) - return - } -} diff --git a/internal/chronicle/adapter/persistence/postgres/query/game.go b/internal/chronicle/adapter/persistence/postgres/query/game.go index 1032678..38cdb4e 100644 --- a/internal/chronicle/adapter/persistence/postgres/query/game.go +++ b/internal/chronicle/adapter/persistence/postgres/query/game.go @@ -3,6 +3,7 @@ package query import ( "context" "strings" + "time" "github.com/SomethingSexy/chronicle/internal/chronicle/adapter/persistence/postgres/sqlc/repository" "github.com/SomethingSexy/chronicle/internal/chronicle/core/domain" @@ -23,10 +24,16 @@ type GameQuery struct { // Create a game func (g GameQuery) CreateGame(ctx context.Context, game domain.Game) (domain.Game, error) { + ts := pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + } args := repository.CreateGameParams{ - GameID: game.GameId, - Name: game.Name, - Type: game.Type, + GameID: game.GameId, + Name: game.Name, + Type: game.Type, + CreatedAt: ts, + UpdatedAt: ts, } response, err := g.Queries.CreateGame(ctx, args) @@ -104,10 +111,16 @@ func (g GameQuery) CreateWorld(ctx context.Context, world domain.World) (domain. return domain.World{}, err } + ts := pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + } args := repository.CreateWorldParams{ - WorldID: world.WorldId, - Name: world.Name, - GameID: game.ID, + WorldID: world.WorldId, + Name: world.Name, + GameID: game.ID, + CreatedAt: ts, + UpdatedAt: ts, } response, err := g.Queries.CreateWorld(ctx, args) @@ -154,6 +167,10 @@ func (g GameQuery) CreateLocation(ctx context.Context, location domain.Location) } } + ts := pgtype.Timestamptz{ + Time: time.Now(), + Valid: true, + } args := repository.CreateLocationParams{ LocationID: location.LocationId, WorldID: world.ID, @@ -164,6 +181,8 @@ func (g GameQuery) CreateLocation(ctx context.Context, location domain.Location) String: path, Valid: true, }, + CreatedAt: ts, + UpdatedAt: ts, } _, err = g.Queries.CreateLocation(ctx, args) diff --git a/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/20240922180859_initial.sql b/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/20240922180859_initial.sql deleted file mode 100644 index 7bd5f52..0000000 --- a/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/20240922180859_initial.sql +++ /dev/null @@ -1,43 +0,0 @@ -CREATE EXTENSION "ltree"; - --- Create "game" table -CREATE TABLE "public"."game" ( - "id" bigserial NOT NULL, - "game_id" uuid NOT NULL, - "name" text NOT NULL, - "type" text NOT NULL, - PRIMARY KEY ("id"), - CONSTRAINT "game_game_id_key" UNIQUE ("game_id") -); --- Create "world" table -CREATE TABLE "public"."world" ( - "id" bigserial NOT NULL, - "world_id" uuid NOT NULL, - "game_id" bigserial NOT NULL, - "name" text NOT NULL, - PRIMARY KEY ("id"), - CONSTRAINT "world_world_id_key" UNIQUE ("world_id"), - CONSTRAINT "world_game_id_fkey" FOREIGN KEY ("game_id") REFERENCES "public"."game" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION -); --- Create index "world_game_id" to table: "world" -CREATE INDEX "world_game_id" ON "public"."world" ("game_id"); --- Create "location" table -CREATE TABLE "public"."location" ( - "id" bigserial NOT NULL, - "location_id" uuid NOT NULL, - "game_id" bigserial NOT NULL, - "world_id" bigserial NOT NULL, - "type" text NOT NULL, - "name" text NOT NULL, - "path" public.ltree NULL, - PRIMARY KEY ("id"), - CONSTRAINT "location_location_id_key" UNIQUE ("location_id"), - CONSTRAINT "location_game_id_fkey" FOREIGN KEY ("game_id") REFERENCES "public"."game" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION, - CONSTRAINT "location_world_id_fkey" FOREIGN KEY ("world_id") REFERENCES "public"."world" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION -); --- Create index "location_game_id" to table: "location" -CREATE INDEX "location_game_id" ON "public"."location" ("game_id"); --- Create index "location_path_idx" to table: "location" -CREATE INDEX "location_path_idx" ON "public"."location" USING gist ("path"); --- Create index "location_world_id" to table: "location" -CREATE INDEX "location_world_id" ON "public"."location" ("world_id"); diff --git a/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/atlas.sum b/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/atlas.sum index 602c5d8..aae3265 100644 --- a/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/atlas.sum +++ b/internal/chronicle/adapter/persistence/postgres/sqlc/migrations/atlas.sum @@ -1,2 +1,2 @@ -h1:PmA+4B4HY9WeN+NxUCd14XupO1EHfzKekMUpCcc+OtA= -20240922180859_initial.sql h1:tDi7JzkIFGrhaZgallRrrYgCFXWJo8ickqK/Xn35gCY= +h1:DljSNBalsjIkrSZ+WOGYFkwLIstGT0W6kK3Zalo3/9Q= +20240924154059_initial.sql h1:YA7/O03b3X56utueibLwOYeFGKNHYWsGZ+3nyM+YWq8= diff --git a/internal/chronicle/adapter/persistence/postgres/sqlc/query.sql b/internal/chronicle/adapter/persistence/postgres/sqlc/query.sql index d1ced78..9bfa2ed 100644 --- a/internal/chronicle/adapter/persistence/postgres/sqlc/query.sql +++ b/internal/chronicle/adapter/persistence/postgres/sqlc/query.sql @@ -12,19 +12,21 @@ ORDER BY name; -- name: CreateGame :one INSERT INTO game ( - game_id, name, type + game_id, name, type, created_at, updated_at ) VALUES ( - $1, $2, $3 + $1, $2, $3, $4, $5 ) ON CONFLICT (game_id) DO UPDATE SET name = EXCLUDED.name, - type = EXCLUDED.type + type = EXCLUDED.type, + updated_at = EXCLUDED.updated_at RETURNING *; -- name: UpdateGame :exec UPDATE game set name = $2, - type = $3 + type = $3, + updated_at = $4 WHERE game_id = $1; -- name: DeleteGame :exec @@ -50,12 +52,13 @@ ORDER BY name; -- name: CreateWorld :one INSERT INTO world ( - world_id, game_id, name + world_id, game_id, name, created_at, updated_at ) VALUES ( - $1, $2, $3 + $1, $2, $3, $4, $5 ) ON CONFLICT (world_id) DO UPDATE SET - name = EXCLUDED.name + name = EXCLUDED.name, + updated_at = EXCLUDED.updated_at RETURNING *; -- name: UpdateWorld :exec @@ -69,14 +72,15 @@ WHERE world_id = $1; -- name: CreateLocation :one INSERT INTO location ( - location_id, world_id, game_id, type, name, path + location_id, world_id, game_id, type, name, path, created_at, updated_at ) VALUES ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7, $8 ) ON CONFLICT (location_id) DO UPDATE SET name = EXCLUDED.name, type = EXCLUDED.type, - path = EXCLUDED.path + path = EXCLUDED.path, + updated_at = EXCLUDED.updated_at RETURNING *; -- name: GetWorldLocations :many @@ -84,4 +88,24 @@ SELECT * FROM location JOIN world ON location.world_id = world.id JOIN game ON location.game_id = game.id WHERE game.game_id = $1 and -world.world_id = $2; \ No newline at end of file +world.world_id = $2; + +-- name: CreateCharacter :one +INSERT INTO character ( + character_id, name, description, created_at, updated_at +) VALUES ( + $1, $2, $3, $4, $5 +) +ON CONFLICT (character_id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + updated_at = EXCLUDED.updated_at +RETURNING *; + +-- name: GetCharacter :one +SELECT * FROM character +WHERE id = $1 LIMIT 1; + +-- name: GetCharacterFromUuid :one +SELECT * FROM character +WHERE character.character_id = $1 LIMIT 1; \ No newline at end of file diff --git a/internal/chronicle/adapter/persistence/postgres/sqlc/repository/models.go b/internal/chronicle/adapter/persistence/postgres/sqlc/repository/models.go index aa0758c..dea0180 100644 --- a/internal/chronicle/adapter/persistence/postgres/sqlc/repository/models.go +++ b/internal/chronicle/adapter/persistence/postgres/sqlc/repository/models.go @@ -9,11 +9,22 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type Character struct { + ID int64 + CharacterID uuid.UUID + Name string + Description pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + type Game struct { - ID int64 - GameID uuid.UUID - Name string - Type string + ID int64 + GameID uuid.UUID + Name string + Type string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } type Location struct { @@ -24,11 +35,15 @@ type Location struct { Type string Name string Path pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } type World struct { - ID int64 - WorldID uuid.UUID - GameID int64 - Name string + ID int64 + WorldID uuid.UUID + GameID int64 + Name string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } diff --git a/internal/chronicle/adapter/persistence/postgres/sqlc/repository/query.sql.go b/internal/chronicle/adapter/persistence/postgres/sqlc/repository/query.sql.go index c4f9766..e9037c9 100644 --- a/internal/chronicle/adapter/persistence/postgres/sqlc/repository/query.sql.go +++ b/internal/chronicle/adapter/persistence/postgres/sqlc/repository/query.sql.go @@ -12,47 +12,100 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const createCharacter = `-- name: CreateCharacter :one +INSERT INTO character ( + character_id, name, description, created_at, updated_at +) VALUES ( + $1, $2, $3, $4, $5 +) +ON CONFLICT (character_id) DO UPDATE SET + name = EXCLUDED.name, + description = EXCLUDED.description, + updated_at = EXCLUDED.updated_at +RETURNING id, character_id, name, description, created_at, updated_at +` + +type CreateCharacterParams struct { + CharacterID uuid.UUID + Name string + Description pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz +} + +func (q *Queries) CreateCharacter(ctx context.Context, arg CreateCharacterParams) (Character, error) { + row := q.db.QueryRow(ctx, createCharacter, + arg.CharacterID, + arg.Name, + arg.Description, + arg.CreatedAt, + arg.UpdatedAt, + ) + var i Character + err := row.Scan( + &i.ID, + &i.CharacterID, + &i.Name, + &i.Description, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const createGame = `-- name: CreateGame :one INSERT INTO game ( - game_id, name, type + game_id, name, type, created_at, updated_at ) VALUES ( - $1, $2, $3 + $1, $2, $3, $4, $5 ) ON CONFLICT (game_id) DO UPDATE SET name = EXCLUDED.name, - type = EXCLUDED.type -RETURNING id, game_id, name, type + type = EXCLUDED.type, + updated_at = EXCLUDED.updated_at +RETURNING id, game_id, name, type, created_at, updated_at ` type CreateGameParams struct { - GameID uuid.UUID - Name string - Type string + GameID uuid.UUID + Name string + Type string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } func (q *Queries) CreateGame(ctx context.Context, arg CreateGameParams) (Game, error) { - row := q.db.QueryRow(ctx, createGame, arg.GameID, arg.Name, arg.Type) + row := q.db.QueryRow(ctx, createGame, + arg.GameID, + arg.Name, + arg.Type, + arg.CreatedAt, + arg.UpdatedAt, + ) var i Game err := row.Scan( &i.ID, &i.GameID, &i.Name, &i.Type, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } const createLocation = `-- name: CreateLocation :one INSERT INTO location ( - location_id, world_id, game_id, type, name, path + location_id, world_id, game_id, type, name, path, created_at, updated_at ) VALUES ( - $1, $2, $3, $4, $5, $6 + $1, $2, $3, $4, $5, $6, $7, $8 ) ON CONFLICT (location_id) DO UPDATE SET name = EXCLUDED.name, type = EXCLUDED.type, - path = EXCLUDED.path -RETURNING id, location_id, game_id, world_id, type, name, path + path = EXCLUDED.path, + updated_at = EXCLUDED.updated_at +RETURNING id, location_id, game_id, world_id, type, name, path, created_at, updated_at ` type CreateLocationParams struct { @@ -62,6 +115,8 @@ type CreateLocationParams struct { Type string Name string Path pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) (Location, error) { @@ -72,6 +127,8 @@ func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) arg.Type, arg.Name, arg.Path, + arg.CreatedAt, + arg.UpdatedAt, ) var i Location err := row.Scan( @@ -82,35 +139,48 @@ func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) &i.Type, &i.Name, &i.Path, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } const createWorld = `-- name: CreateWorld :one INSERT INTO world ( - world_id, game_id, name + world_id, game_id, name, created_at, updated_at ) VALUES ( - $1, $2, $3 + $1, $2, $3, $4, $5 ) ON CONFLICT (world_id) DO UPDATE SET - name = EXCLUDED.name -RETURNING id, world_id, game_id, name + name = EXCLUDED.name, + updated_at = EXCLUDED.updated_at +RETURNING id, world_id, game_id, name, created_at, updated_at ` type CreateWorldParams struct { - WorldID uuid.UUID - GameID int64 - Name string + WorldID uuid.UUID + GameID int64 + Name string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz } func (q *Queries) CreateWorld(ctx context.Context, arg CreateWorldParams) (World, error) { - row := q.db.QueryRow(ctx, createWorld, arg.WorldID, arg.GameID, arg.Name) + row := q.db.QueryRow(ctx, createWorld, + arg.WorldID, + arg.GameID, + arg.Name, + arg.CreatedAt, + arg.UpdatedAt, + ) var i World err := row.Scan( &i.ID, &i.WorldID, &i.GameID, &i.Name, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } @@ -135,8 +205,46 @@ func (q *Queries) DeleteWorld(ctx context.Context, worldID uuid.UUID) error { return err } +const getCharacter = `-- name: GetCharacter :one +SELECT id, character_id, name, description, created_at, updated_at FROM character +WHERE id = $1 LIMIT 1 +` + +func (q *Queries) GetCharacter(ctx context.Context, id int64) (Character, error) { + row := q.db.QueryRow(ctx, getCharacter, id) + var i Character + err := row.Scan( + &i.ID, + &i.CharacterID, + &i.Name, + &i.Description, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getCharacterFromUuid = `-- name: GetCharacterFromUuid :one +SELECT id, character_id, name, description, created_at, updated_at FROM character +WHERE character.character_id = $1 LIMIT 1 +` + +func (q *Queries) GetCharacterFromUuid(ctx context.Context, characterID uuid.UUID) (Character, error) { + row := q.db.QueryRow(ctx, getCharacterFromUuid, characterID) + var i Character + err := row.Scan( + &i.ID, + &i.CharacterID, + &i.Name, + &i.Description, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const getGame = `-- name: GetGame :one -SELECT id, game_id, name, type FROM game +SELECT id, game_id, name, type, created_at, updated_at FROM game WHERE id = $1 LIMIT 1 ` @@ -148,12 +256,14 @@ func (q *Queries) GetGame(ctx context.Context, id int64) (Game, error) { &i.GameID, &i.Name, &i.Type, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } const getGameFromUuid = `-- name: GetGameFromUuid :one -SELECT id, game_id, name, type FROM game +SELECT id, game_id, name, type, created_at, updated_at FROM game WHERE game.game_id = $1 LIMIT 1 ` @@ -165,25 +275,31 @@ func (q *Queries) GetGameFromUuid(ctx context.Context, gameID uuid.UUID) (Game, &i.GameID, &i.Name, &i.Type, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } const getGameWorlds = `-- name: GetGameWorlds :many -SELECT world.id, world_id, world.game_id, world.name, game.id, game.game_id, game.name, type FROM world +SELECT world.id, world_id, world.game_id, world.name, world.created_at, world.updated_at, game.id, game.game_id, game.name, type, game.created_at, game.updated_at FROM world JOIN game ON world.game_id = game.id WHERE game.game_id = $1 ` type GetGameWorldsRow struct { - ID int64 - WorldID uuid.UUID - GameID int64 - Name string - ID_2 int64 - GameID_2 uuid.UUID - Name_2 string - Type string + ID int64 + WorldID uuid.UUID + GameID int64 + Name string + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz + ID_2 int64 + GameID_2 uuid.UUID + Name_2 string + Type string + CreatedAt_2 pgtype.Timestamptz + UpdatedAt_2 pgtype.Timestamptz } func (q *Queries) GetGameWorlds(ctx context.Context, gameID uuid.UUID) ([]GetGameWorldsRow, error) { @@ -200,10 +316,14 @@ func (q *Queries) GetGameWorlds(ctx context.Context, gameID uuid.UUID) ([]GetGam &i.WorldID, &i.GameID, &i.Name, + &i.CreatedAt, + &i.UpdatedAt, &i.ID_2, &i.GameID_2, &i.Name_2, &i.Type, + &i.CreatedAt_2, + &i.UpdatedAt_2, ); err != nil { return nil, err } @@ -216,7 +336,7 @@ func (q *Queries) GetGameWorlds(ctx context.Context, gameID uuid.UUID) ([]GetGam } const getWorld = `-- name: GetWorld :one -SELECT id, world_id, game_id, name FROM world +SELECT id, world_id, game_id, name, created_at, updated_at FROM world WHERE id = $1 LIMIT 1 ` @@ -228,12 +348,14 @@ func (q *Queries) GetWorld(ctx context.Context, id int64) (World, error) { &i.WorldID, &i.GameID, &i.Name, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } const getWorldFromUuid = `-- name: GetWorldFromUuid :one -SELECT id, world_id, game_id, name FROM world +SELECT id, world_id, game_id, name, created_at, updated_at FROM world WHERE world_id = $1 LIMIT 1 ` @@ -245,12 +367,14 @@ func (q *Queries) GetWorldFromUuid(ctx context.Context, worldID uuid.UUID) (Worl &i.WorldID, &i.GameID, &i.Name, + &i.CreatedAt, + &i.UpdatedAt, ) return i, err } const getWorldLocations = `-- name: GetWorldLocations :many -SELECT location.id, location_id, location.game_id, location.world_id, location.type, location.name, path, world.id, world.world_id, world.game_id, world.name, game.id, game.game_id, game.name, game.type FROM location +SELECT location.id, location_id, location.game_id, location.world_id, location.type, location.name, path, location.created_at, location.updated_at, world.id, world.world_id, world.game_id, world.name, world.created_at, world.updated_at, game.id, game.game_id, game.name, game.type, game.created_at, game.updated_at FROM location JOIN world ON location.world_id = world.id JOIN game ON location.game_id = game.id WHERE game.game_id = $1 and @@ -263,21 +387,27 @@ type GetWorldLocationsParams struct { } type GetWorldLocationsRow struct { - ID int64 - LocationID uuid.UUID - GameID int64 - WorldID int64 - Type string - Name string - Path pgtype.Text - ID_2 int64 - WorldID_2 uuid.UUID - GameID_2 int64 - Name_2 string - ID_3 int64 - GameID_3 uuid.UUID - Name_3 string - Type_2 string + ID int64 + LocationID uuid.UUID + GameID int64 + WorldID int64 + Type string + Name string + Path pgtype.Text + CreatedAt pgtype.Timestamptz + UpdatedAt pgtype.Timestamptz + ID_2 int64 + WorldID_2 uuid.UUID + GameID_2 int64 + Name_2 string + CreatedAt_2 pgtype.Timestamptz + UpdatedAt_2 pgtype.Timestamptz + ID_3 int64 + GameID_3 uuid.UUID + Name_3 string + Type_2 string + CreatedAt_3 pgtype.Timestamptz + UpdatedAt_3 pgtype.Timestamptz } func (q *Queries) GetWorldLocations(ctx context.Context, arg GetWorldLocationsParams) ([]GetWorldLocationsRow, error) { @@ -297,14 +427,20 @@ func (q *Queries) GetWorldLocations(ctx context.Context, arg GetWorldLocationsPa &i.Type, &i.Name, &i.Path, + &i.CreatedAt, + &i.UpdatedAt, &i.ID_2, &i.WorldID_2, &i.GameID_2, &i.Name_2, + &i.CreatedAt_2, + &i.UpdatedAt_2, &i.ID_3, &i.GameID_3, &i.Name_3, &i.Type_2, + &i.CreatedAt_3, + &i.UpdatedAt_3, ); err != nil { return nil, err } @@ -317,7 +453,7 @@ func (q *Queries) GetWorldLocations(ctx context.Context, arg GetWorldLocationsPa } const listGames = `-- name: ListGames :many -SELECT id, game_id, name, type FROM game +SELECT id, game_id, name, type, created_at, updated_at FROM game ORDER BY name ` @@ -335,6 +471,8 @@ func (q *Queries) ListGames(ctx context.Context) ([]Game, error) { &i.GameID, &i.Name, &i.Type, + &i.CreatedAt, + &i.UpdatedAt, ); err != nil { return nil, err } @@ -347,7 +485,7 @@ func (q *Queries) ListGames(ctx context.Context) ([]Game, error) { } const listWorlds = `-- name: ListWorlds :many -SELECT id, world_id, game_id, name FROM world +SELECT id, world_id, game_id, name, created_at, updated_at FROM world ORDER BY name ` @@ -365,6 +503,8 @@ func (q *Queries) ListWorlds(ctx context.Context) ([]World, error) { &i.WorldID, &i.GameID, &i.Name, + &i.CreatedAt, + &i.UpdatedAt, ); err != nil { return nil, err } @@ -379,18 +519,25 @@ func (q *Queries) ListWorlds(ctx context.Context) ([]World, error) { const updateGame = `-- name: UpdateGame :exec UPDATE game set name = $2, - type = $3 + type = $3, + updated_at = $4 WHERE game_id = $1 ` type UpdateGameParams struct { - GameID uuid.UUID - Name string - Type string + GameID uuid.UUID + Name string + Type string + UpdatedAt pgtype.Timestamptz } func (q *Queries) UpdateGame(ctx context.Context, arg UpdateGameParams) error { - _, err := q.db.Exec(ctx, updateGame, arg.GameID, arg.Name, arg.Type) + _, err := q.db.Exec(ctx, updateGame, + arg.GameID, + arg.Name, + arg.Type, + arg.UpdatedAt, + ) return err } diff --git a/internal/chronicle/adapter/persistence/postgres/sqlc/schema.sql b/internal/chronicle/adapter/persistence/postgres/sqlc/schema.sql index 5f8934b..cfa5d78 100644 --- a/internal/chronicle/adapter/persistence/postgres/sqlc/schema.sql +++ b/internal/chronicle/adapter/persistence/postgres/sqlc/schema.sql @@ -5,7 +5,9 @@ CREATE TABLE game ( id BIGSERIAL PRIMARY KEY, game_id uuid UNIQUE NOT NULL, name text NOT NULL, - type text NOT NULL + type text NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Represents the game world in general @@ -14,7 +16,9 @@ CREATE TABLE world ( id BIGSERIAL PRIMARY KEY, world_id uuid UNIQUE NOT NULL, game_id BIGSERIAL NOT NULL REFERENCES game(id), - name text NOT NULL + name text NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); create index world_game_id on world(game_id); @@ -27,9 +31,20 @@ CREATE TABLE location ( world_id BIGSERIAL NOT NULL REFERENCES world(id), type text NOT NULL, name text NOT NULL, - path ltree + path ltree, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); create index location_game_id on location(game_id); create index location_world_id on location(world_id); -create index location_path_idx on location using gist (path); \ No newline at end of file +create index location_path_idx on location using gist (path); + +CREATE TABLE character ( + id BIGSERIAL PRIMARY KEY, + character_id uuid UNIQUE NOT NULL, + name text NOT NULL, + description text, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); \ No newline at end of file diff --git a/internal/chronicle/core/application/application.go b/internal/chronicle/core/application/application.go index f2cc21f..0eda07f 100644 --- a/internal/chronicle/core/application/application.go +++ b/internal/chronicle/core/application/application.go @@ -6,18 +6,18 @@ import ( "github.com/SomethingSexy/chronicle/internal/chronicle/port" ) -func NewApplication(persistence port.ChronicleQueries) port.ChronicleApplication { +func NewApplication(persistence port.Persistence) port.ChronicleApplication { commands := port.ChronicleCommands{ - CreateGame: command.NewCreateGameCommand(persistence), - CreateWorld: command.NewCreateWorldCommand(persistence), - CreateLocation: command.NewCreateLocationCommand(persistence), + CreateGame: command.NewCreateGameCommand(persistence.Game), + CreateWorld: command.NewCreateWorldCommand(persistence.Game), + CreateLocation: command.NewCreateLocationCommand(persistence.Game), } queries := port.GameQueries{ - ListGames: query.NewListGamesHandler(persistence), - GetGame: query.NewGetGameHandler(persistence), - ListLocations: query.NewListLocationsHandler(persistence), - GetWorld: query.NewGetWorldHandler(persistence), + ListGames: query.NewListGamesHandler(persistence.Game), + GetGame: query.NewGetGameHandler(persistence.Game), + ListLocations: query.NewListLocationsHandler(persistence.Game), + GetWorld: query.NewGetWorldHandler(persistence.Game), } return port.ChronicleApplication{ diff --git a/internal/chronicle/port/chronicle.go b/internal/chronicle/port/chronicle.go deleted file mode 100644 index 9ed3898..0000000 --- a/internal/chronicle/port/chronicle.go +++ /dev/null @@ -1,23 +0,0 @@ -package port - -import ( - "context" - - "github.com/SomethingSexy/chronicle/internal/chronicle/core/domain" - "github.com/google/uuid" -) - -// TODO: Probably a better name for this -type ChronicleQueries interface { - CreateGame(ctx context.Context, game domain.Game) (domain.Game, error) - ListGames(ctx context.Context) ([]domain.Game, error) - GetGame(ctx context.Context, id uuid.UUID) (domain.Game, error) - - GetGameWorlds(ctx context.Context, gameId uuid.UUID) ([]domain.World, error) - - CreateWorld(ctx context.Context, world domain.World) (domain.World, error) - GetWorld(ctx context.Context, gameId uuid.UUID, worldId uuid.UUID) (domain.World, error) - - CreateLocation(ct context.Context, location domain.Location) (domain.Location, error) - ListLocations(ctx context.Context, gameId uuid.UUID, worldId uuid.UUID) ([]domain.Location, error) -} diff --git a/internal/chronicle/port/service.go b/internal/chronicle/port/service.go index 75d2a9e..6ac2d2e 100644 --- a/internal/chronicle/port/service.go +++ b/internal/chronicle/port/service.go @@ -7,7 +7,7 @@ import ( type ChronicleApplication struct { Commands ChronicleCommands Queries GameQueries - Persistence ChronicleQueries + Persistence Persistence } type ChronicleCommands struct { diff --git a/internal/chronicle/service/application.go b/internal/chronicle/service/application.go index 3c8ca22..fea9030 100644 --- a/internal/chronicle/service/application.go +++ b/internal/chronicle/service/application.go @@ -9,7 +9,7 @@ import ( "github.com/SomethingSexy/chronicle/internal/chronicle/adapter/http/game" "github.com/SomethingSexy/chronicle/internal/chronicle/adapter/persistence/postgres/query" "github.com/SomethingSexy/chronicle/internal/chronicle/adapter/persistence/postgres/sqlc/repository" - gameApplication "github.com/SomethingSexy/chronicle/internal/chronicle/core/application" + "github.com/SomethingSexy/chronicle/internal/chronicle/core/application" "github.com/SomethingSexy/chronicle/internal/chronicle/port" "github.com/go-chi/chi/v5" "github.com/jackc/pgx/v5/pgxpool" @@ -27,12 +27,15 @@ func NewService() { q := repository.New(db) // TODO: We will need to create these individally as we add and merge together - chronicleQueries := query.NewGameQuery(q) + persistence := port.Persistence{ + Game: query.NewGameQuery(q), + Character: query.NewCharacterQuery(q), + } - game := gameApplication.NewApplication(chronicleQueries) + app := application.NewApplication(persistence) service := ChronicleService{ - ChronicleApplication: game, + ChronicleApplication: app, } httpServer := http.NewHttpServer(service)