Skip to content

feat(world-builder): stage 2 map objects and structures API (#90) - #106

Open
Rodrigoue9 wants to merge 2 commits into
Bitcoindefi:mainfrom
Rodrigoue9:feat/bounty-90
Open

feat(world-builder): stage 2 map objects and structures API (#90)#106
Rodrigoue9 wants to merge 2 commits into
Bitcoindefi:mainfrom
Rodrigoue9:feat/bounty-90

Conversation

@Rodrigoue9

Copy link
Copy Markdown

Title

feat(world-builder): stage 2 map objects, doors and structures API (#90)

Description

  • Implements api/src/repositories/mapObjects.ts with placeObject, moveObject, removeObject, getMapObjects, setObjectState, and atomic placeStructure.
  • Exposes internal REST endpoints /internal/map-objects for map editor object lifecycle management.
  • Validates grid bounds (1-100) and ensures non-zero objIndex.

Closes #90

Comment thread api/src/repositories/mapObjects.ts
Comment thread api/src/repositories/mapObjects.ts Outdated
Comment on lines +40 to +52
state: MapObjectState = "placed"
): Promise<MapObjectRecord> {
validateCoordinates(x, y);
if (objIndex <= 0) {
throw new Error("objIndex debe ser mayor a 0");
}

const res = await pool.query(
`INSERT INTO game_map_objects (map_id, x, y, obj_index, amount, state, created_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, map_id AS "mapId", x, y, obj_index AS "objIndex", amount, state, created_by AS "createdBy", created_at AS "createdAt"`,
[mapId, x, y, objIndex, amount, state, createdBy]
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: state is not validated against allowed MapObjectState values

placeObject, setObjectState, and placeStructure write the client-supplied state directly to the DB. MapObjectState is a compile-time-only TypeScript union, so any arbitrary string from the request body is accepted and persisted, corrupting object state semantics (door_open/door_closed/etc.). Validate state against an explicit runtime allow-list (or add a CHECK constraint / enum on the column) and reject unknown values.

Was this helpful? React with 👍 / 👎

Comment thread api/src/server.ts
Comment on lines +2907 to +2918
app.post("/internal/map-objects", requireAuth, async (request, response) => {
try {
const { mapId, x, y, objIndex, amount, createdBy, state } = request.body;
const { placeObject } = await import("./repositories/mapObjects");
const result = await placeObject(mapId, x, y, objIndex, amount, createdBy, state);
response.status(201).json(result);
} catch (error) {
response.status(400).json({
error: error instanceof Error ? error.message : "Unexpected error",
});
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Security: Editor write endpoints use static-token requireAuth, not admin auth

The new /internal/map-objects write endpoints (place/move/delete/state/structure) mutate map/world data but are guarded only by requireAuth, which just compares the Authorization header to a single static config.tokenAuth. Comparable game-data/map-editing mutations in this server use requireAdminEmailSession + isAuthorizedGameDataAdmin. If these endpoints are reachable by regular clients (not only trusted server-to-server callers), anyone holding the shared token can place/move/delete world objects. Confirm the intended caller; if end users can reach these routes, switch to admin session authorization.

Was this helpful? React with 👍 / 👎

Comment thread api/schema.sql
y INTEGER NOT NULL CHECK (y BETWEEN 1 AND 100),
obj_index INTEGER NOT NULL CHECK (obj_index > 0),
amount INTEGER NOT NULL DEFAULT 1 CHECK (amount > 0),
state VARCHAR(50) NOT NULL DEFAULT 'default' CHECK (state IN ('default', 'door_open', 'door_closed', 'locked', 'destroyed')),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Bug: state CHECK constraint disagrees with allowed app states

The DB CHECK on game_map_objects.state permits ('default','door_open','door_closed','locked','destroyed'), but the application's ALLOWED_MAP_OBJECT_STATES / MapObjectState are ('placed','structure','door_open','door_closed','sign'). validateState() will accept 'placed', 'structure', and 'sign' (including the defaults used by placeObject → 'placed' and placeStructure → 'structure'), so the INSERT will violate the CHECK constraint and throw at the DB layer — placeObject and placeStructure fail for every default call. Align the two lists: update the schema CHECK to IN ('placed','structure','door_open','door_closed','sign') and set the column DEFAULT to 'placed' (or 'structure').

Match the schema CHECK/DEFAULT to the application's MapObjectState values.:

state VARCHAR(50) NOT NULL DEFAULT 'placed' CHECK (state IN ('placed', 'structure', 'door_open', 'door_closed', 'sign')),
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown
Code Review 🚫 Blocked 2 resolved / 5 findings

Implements stage 2 map objects and structures API with grid validation and lifecycle endpoints, but the state column allows unvalidated values, the database CHECK constraint disagrees with application states, and write endpoints rely on a weak static-token auth guard instead of admin permissions.

🚨 Bug: state CHECK constraint disagrees with allowed app states

📄 api/schema.sql:638 📄 api/src/repositories/mapObjects.ts:27-33 📄 api/src/repositories/mapObjects.ts:54 📄 api/src/repositories/mapObjects.ts:66 📄 api/src/repositories/mapObjects.ts:143-144

The DB CHECK on game_map_objects.state permits ('default','door_open','door_closed','locked','destroyed'), but the application's ALLOWED_MAP_OBJECT_STATES / MapObjectState are ('placed','structure','door_open','door_closed','sign'). validateState() will accept 'placed', 'structure', and 'sign' (including the defaults used by placeObject → 'placed' and placeStructure → 'structure'), so the INSERT will violate the CHECK constraint and throw at the DB layer — placeObject and placeStructure fail for every default call. Align the two lists: update the schema CHECK to IN ('placed','structure','door_open','door_closed','sign') and set the column DEFAULT to 'placed' (or 'structure').

Match the schema CHECK/DEFAULT to the application's MapObjectState values.
state VARCHAR(50) NOT NULL DEFAULT 'placed' CHECK (state IN ('placed', 'structure', 'door_open', 'door_closed', 'sign')),
⚠️ Bug: state is not validated against allowed MapObjectState values

📄 api/src/repositories/mapObjects.ts:40-52 📄 api/src/repositories/mapObjects.ts:84-92 📄 api/src/repositories/mapObjects.ts:115

placeObject, setObjectState, and placeStructure write the client-supplied state directly to the DB. MapObjectState is a compile-time-only TypeScript union, so any arbitrary string from the request body is accepted and persisted, corrupting object state semantics (door_open/door_closed/etc.). Validate state against an explicit runtime allow-list (or add a CHECK constraint / enum on the column) and reject unknown values.

⚠️ Security: Editor write endpoints use static-token requireAuth, not admin auth

📄 api/src/server.ts:2907-2918 📄 api/src/server.ts:2920-2934 📄 api/src/server.ts:2937-2951 📄 api/src/server.ts:2953-2967 📄 api/src/server.ts:2970-2981

The new /internal/map-objects write endpoints (place/move/delete/state/structure) mutate map/world data but are guarded only by requireAuth, which just compares the Authorization header to a single static config.tokenAuth. Comparable game-data/map-editing mutations in this server use requireAdminEmailSession + isAuthorizedGameDataAdmin. If these endpoints are reachable by regular clients (not only trusted server-to-server callers), anyone holding the shared token can place/move/delete world objects. Confirm the intended caller; if end users can reach these routes, switch to admin session authorization.

✅ 2 resolved
Bug: Table game_map_objects is never created in schema

📄 api/src/repositories/mapObjects.ts:47-53 📄 api/src/repositories/mapObjects.ts:74-81
Every function in mapObjects.ts queries game_map_objects, but that table is not defined anywhere in the repo (api/schema.sql defines all other tables including game_map_tile_overrides, but not this one). All six endpoints will fail at runtime with a "relation "game_map_objects" does not exist" Postgres error, so the entire feature is non-functional. Add a CREATE TABLE IF NOT EXISTS game_map_objects (...) to api/schema.sql with the id/map_id/x/y/obj_index/amount/state/created_by/created_at columns used here.

Edge Case: No numeric validation of request params/body before queries

📄 api/src/repositories/mapObjects.ts:27-31 📄 api/src/server.ts:2896 📄 api/src/server.ts:2909-2911 📄 api/src/server.ts:2922-2925 📄 api/src/server.ts:2939 📄 api/src/server.ts:2956 📄 api/src/server.ts:2972-2974
Number(request.params.mapId) / Number(request.params.id) yield NaN for non-numeric input, and body fields (x, y, objIndex, amount) are passed straight through without type checks. Because validateCoordinates uses </> comparisons, a non-numeric value such as "abc" passes both checks ("abc" < 1 and "abc" > 100 are both false), bypassing bounds validation entirely. Coerce and validate mapId/id/x/y/objIndex/amount as finite integers (e.g. Number.isInteger) and reject invalid input with a 400 before hitting the DB.

🤖 Prompt for agents
Code Review: Implements stage 2 map objects and structures API with grid validation and lifecycle endpoints, but the state column allows unvalidated values, the database CHECK constraint disagrees with application states, and write endpoints rely on a weak static-token auth guard instead of admin permissions.

1. ⚠️ Bug: state is not validated against allowed MapObjectState values
   Files: api/src/repositories/mapObjects.ts:40-52, api/src/repositories/mapObjects.ts:84-92, api/src/repositories/mapObjects.ts:115

   `placeObject`, `setObjectState`, and `placeStructure` write the client-supplied `state` directly to the DB. `MapObjectState` is a compile-time-only TypeScript union, so any arbitrary string from the request body is accepted and persisted, corrupting object state semantics (door_open/door_closed/etc.). Validate `state` against an explicit runtime allow-list (or add a CHECK constraint / enum on the column) and reject unknown values.

2. ⚠️ Security: Editor write endpoints use static-token requireAuth, not admin auth
   Files: api/src/server.ts:2907-2918, api/src/server.ts:2920-2934, api/src/server.ts:2937-2951, api/src/server.ts:2953-2967, api/src/server.ts:2970-2981

   The new /internal/map-objects write endpoints (place/move/delete/state/structure) mutate map/world data but are guarded only by `requireAuth`, which just compares the Authorization header to a single static `config.tokenAuth`. Comparable game-data/map-editing mutations in this server use `requireAdminEmailSession` + `isAuthorizedGameDataAdmin`. If these endpoints are reachable by regular clients (not only trusted server-to-server callers), anyone holding the shared token can place/move/delete world objects. Confirm the intended caller; if end users can reach these routes, switch to admin session authorization.

3. 🚨 Bug: state CHECK constraint disagrees with allowed app states
   Files: api/schema.sql:638, api/src/repositories/mapObjects.ts:27-33, api/src/repositories/mapObjects.ts:54, api/src/repositories/mapObjects.ts:66, api/src/repositories/mapObjects.ts:143-144

   The DB CHECK on game_map_objects.state permits ('default','door_open','door_closed','locked','destroyed'), but the application's ALLOWED_MAP_OBJECT_STATES / MapObjectState are ('placed','structure','door_open','door_closed','sign'). validateState() will accept 'placed', 'structure', and 'sign' (including the defaults used by placeObject → 'placed' and placeStructure → 'structure'), so the INSERT will violate the CHECK constraint and throw at the DB layer — placeObject and placeStructure fail for every default call. Align the two lists: update the schema CHECK to IN ('placed','structure','door_open','door_closed','sign') and set the column DEFAULT to 'placed' (or 'structure').

   Fix (Match the schema CHECK/DEFAULT to the application's MapObjectState values.):
   state VARCHAR(50) NOT NULL DEFAULT 'placed' CHECK (state IN ('placed', 'structure', 'door_open', 'door_closed', 'sign')),

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant