diff --git a/API.md b/API.md new file mode 100644 index 0000000..6e1907f --- /dev/null +++ b/API.md @@ -0,0 +1,163 @@ +## /generateAccessToken + +**URL: POST** /generateAccessToken + +**DESCRIPTION:** Used for generating new access tokens. Type X access token required via Authorization header + +**BODY:** +```json +{ + "username": "string" +} +``` + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK", + "accessToken": "A.MTcxODcxNDg2MTg5Mw.NjUzZTk2NzdlNWMzZDM2YjE1YmE3NGVi.QilYuiOT_svRX2iN7f7-jw" +} + +``` + +## /testAccessToken + +**URL: GET** /testAccessToken + +**DESCRIPTION:** Used for testing access token validity. Type A access token required via Authorization header + +**BODY:** N/A + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK", + "data": "Access token valid for user gargamel" +} +``` + +## /planets/getPlanet + +**URL: GET** /planets/getPlanet/*:planetId* + +**DESCRIPTION:** Used to get data about specific planet with ID provided in URL. Type A access token required via Authorization header + +**BODY:** N/A + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK", + "data": { + "id": "661d1163567111a5b4be8829", + "owner": "653e9677e5c3d36b15ba74eb", + "name": "Lyoko", + "fields": 20, + "resources": [], + "buildings": [], + "fleet": [], + } +} +``` + +## /planets/getAllPlanets + +**URL: GET** /planets/getAllPlanets/ + +**DESCRIPTION:** Limited view of all planets of all players + +**BODY:** N/A + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK", + "data": [ + { + "planetId": "661d1163567111a5b4be8829", + "ownerId": "653e9677e5c3d36b15ba74eb", + "name": "Lyoko" + }, + { + "planetId": "666af607f0ae1e11d2d03544", + "ownerId": "653e9677e5c3d36b15ba74eb", + "name": "Cortex" + } + ] +} +``` + +## /research/getResearch + +**URL: GET** /research/getResearch + +**DESCRIPTION:** Used to get data about user research (which technologies are unlocked and on which level). Type A access token required via Authorization header + +**BODY:** N/A + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK", + "data": [ + { + "id": "basic-engine", + "level": 7 + }, + { + "id": "advanced-engine", + "level": 1 + } + ] +} +``` + +## /research/performResearch + +**URL: POST** /research/performResearch + +**DESCRIPTION:** Used to send request to start researching. Required buildings and resources are checked on planet provided in body. Type A access token required via Authorization header + +**BODY:** +```json +{ + "research": "string", + "planetId": "string" +} +``` + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK" +} +``` + +## /build/createBuilding + +**URL: POST** /build/createBuilding + +**DESCRIPTION:** Used to send request to build or upgrade building on planet. Required buildings and resources are checked on planet provided in body. Type A access token required via Authorization header + +**BODY:** +```json +{ + "building": "string", + "planetId": "string" +} +``` + +**OUTPUT:** +```json +{ + "code": 200, + "message": "OK" +} +``` + diff --git a/src/lib/db/planets.ts b/src/lib/db/planets.ts index 0182b4b..9229566 100644 --- a/src/lib/db/planets.ts +++ b/src/lib/db/planets.ts @@ -45,4 +45,25 @@ export const getPlanetById = async (id: ObjectId) => { return planets.findOne({ _id: id }) as Promise; +} + +export const createOrUpgradeBuilding = async (planetId: ObjectId, building: Building) => { + const planet = await getPlanetById(planetId); + if(!planet) throw new Error("Planet not found"); + + const buildingIndex = planet.buildings.findIndex(b => b.id === building.id); + if(buildingIndex === -1) { + planet.buildings.push(building); + } else { + planet.buildings[buildingIndex].level++; + } + + const planets = await Planets(); + await planets.updateOne({ + _id: planetId + }, { + $set: { + buildings: planet.buildings + } + }) } \ No newline at end of file diff --git a/src/pages/api/build/createBuilding.ts b/src/pages/api/build/createBuilding.ts index cab5387..695b563 100644 --- a/src/pages/api/build/createBuilding.ts +++ b/src/pages/api/build/createBuilding.ts @@ -1,11 +1,12 @@ -import { type APIRoute } from "astro"; +import { build, type APIRoute } from "astro"; import validateAccessToken from "../../../lib/utils/validateAccessToken"; -import { getAccessToken } from "../../../lib/db/accessTokens"; -// import { getUserResources } from "../../../lib/db/users"; -import { getUserResources } from "../../../lib/utils/resourceManager"; +import { calculateCurrentAvailableResources, updatePlanetResources } from "../../../lib/utils/resourceManager"; import buildings from '../../../lib/data/buildings.json'; -import calculateAvailableResources from "../../../lib/utils/calculateAvailableResources"; -import { getUserByAccessToken } from "../../../lib/db/users"; +import { getUserByAccessToken, getUserResearch } from "../../../lib/db/users"; +import Planet from "../../../types/Planet"; +import { createOrUpgradeBuilding, getPlanetById } from "../../../lib/db/planets"; +import { ObjectId } from "mongodb"; +import DBResource from "../../../types/DBResource"; export const POST: APIRoute = async({ request }) => { const response = await validateAccessToken(request); @@ -20,21 +21,135 @@ export const POST: APIRoute = async({ request }) => { }), { status: 401 } ) } - const resources = await getUserResources(user._id); - const buildingId = (await request.json()).building; - const building = buildings.map(cat => cat.buildings.filter(b => b.id === buildingId))[0][0]; - const balance = calculateAvailableResources(resources, building.requirements.resources); + + let body; + try { + body = await request.json() + } catch(e) { + return new Response( + JSON.stringify({ + code: 400, + message: "Bad Request", + error: "Invalid JSON body" + }), { status: 400 } + ) + } + + const buildingId = body.building; + let buildingData: {id: string, name: string, requirements: { buildings: Array<{}>, research: Array<{}>, resources: Array }, multiplier: number} | null = null; + buildings.forEach((category: any) => { + // console.log(category.buildings.filter((element: any) => element.id === buildingId)[0]); + if(buildingData !== null) return; + buildingData = category.buildings.find((element: any) => element.id === buildingId); + }); + if(!buildingData) { + return new Response( + JSON.stringify({ + code: 400, + message: "Bad Request", + error: "Invalid building id" + }), { status: 400 } + ) + } + buildingData = buildingData as {id: string, name: string, requirements: { buildings: Array<{}>, research: Array<{}>, resources: Array }, multiplier: number}; // fuck you typescript + let userPlanet: Planet | null; + try { + userPlanet = await getPlanetById(new ObjectId(body.planetId)); + } catch(e) { + return new Response( + JSON.stringify({ + code: 400, + message: "Bad Request", + error: "Invalid planet id" + }), { status: 400 } + ) + } + if(!userPlanet) { + return new Response( + JSON.stringify({ + code: 404, + message: "Not Found", + error: "Planet not found" + }), { status: 404 } + ) + } + // check requirements + // buildings + const playerBuildings = userPlanet.buildings; + buildingData.requirements.buildings.forEach((buildingReq: any) => { + if(playerBuildings.filter((building) => building.id === buildingReq.id)[0].level < buildingReq.level) { + return new Response( + JSON.stringify({ + code: 400, + message: "Bad Request", + error: `${buildingReq.id} level ${buildingReq.level} required, found ${playerBuildings.filter((building) => building.id === buildingReq.id)[0].level}` + }), { status: 400 } + ) + } + }); + + // research + const playerResearch = await getUserResearch(user); + buildingData.requirements.research.forEach((researchReq: any) => { + if(playerResearch.filter((research) => research.id === researchReq.id)[0].level < researchReq.level) { + return new Response( + JSON.stringify({ + code: 400, + message: "Bad Request", + error: `${researchReq.id} level ${researchReq.level} required, found ${playerResearch.filter((research) => research.id === researchReq.id)[0].level}` + }), { status: 400 } + ) + } + }); + + // resources + const resources = await calculateCurrentAvailableResources(userPlanet._id); + if(!resources) { + return new Response( + JSON.stringify({ + code: 500, + message: "Internal Server Error", + error: "Failed to get resources" + }), { status: 500 } + ) + } + const playerCurrentResearch = playerResearch.filter((element: any) => element.id === buildingId)[0]; + const level = playerCurrentResearch ? playerCurrentResearch.level : 0; + const newResources = structuredClone(resources); + const missingResources: Array<{}> = []; + Object.entries(buildingData.requirements.resources).forEach(([key, value]) => { + const res = resources.filter((element: DBResource) => element.name === value.name)[0]; + const cost = playerCurrentResearch ? value.amount * Math.pow((buildingData as {multiplier: number}).multiplier, level) : value.amount; + + if(res.amount < cost) { + missingResources.push({ + name: key, + required: cost, + available: res.amount + }); + return; + } + else newResources.filter((element: DBResource) => element.name === value.name)[0].amount -= cost; + }); + + if(missingResources.length > 0) { + return new Response( + JSON.stringify({ + code: 400, + message: "Bad Request", + data: missingResources + }), { status: 400 } + ) + } + + await updatePlanetResources(userPlanet._id, newResources); + await createOrUpgradeBuilding(userPlanet._id, { id: buildingId, level: level + 1 }); + return new Response( JSON.stringify({ code: 200, - message: "OK", - accessToken: response, - data: { - resources, - building, - balance - } + message: "OK" }), { status: 200 } ) } \ No newline at end of file diff --git a/src/pages/api/build/getBuildings.ts b/src/pages/api/build/getBuildings.ts deleted file mode 100644 index a1eccc5..0000000 --- a/src/pages/api/build/getBuildings.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { APIRoute } from "astro"; -import { getAccessToken } from "../../../lib/db/accessTokens"; -import validateAccessToken from "../../../lib/utils/validateAccessToken"; - -export const GET: APIRoute = async({ request }) => { - const response = await validateAccessToken(request); - if(response instanceof Response) return response; - - const buildings = [ - { - "name": "Iron Mine", - "level": 1, - "production": 100 - }, - { - "name": "Coal Mine", - "level": 2, - "production": 150 - } - ]; - - return new Response( - JSON.stringify({ - code: 200, - message: "OK", - data: { - buildings - } - }) - ); -} \ No newline at end of file