diff --git a/src/index.ts b/src/index.ts index 2951eac..c3e6e32 100644 --- a/src/index.ts +++ b/src/index.ts @@ -72,10 +72,10 @@ router.use(express.json()); router.use(cors()); router.use(helmet()); -// A timer that runs every 10 minutes +// A timer that runs every 1 minute setInterval(() => { network.updateCache(); -}, 10 * 60 * 1000); +}, 1 * 60 * 1000); app.use("/", router); diff --git a/src/routes/plotsystem/teams/cities/GET.ts b/src/routes/plotsystem/teams/cities/GET.ts index e0efe81..994f5c7 100644 --- a/src/routes/plotsystem/teams/cities/GET.ts +++ b/src/routes/plotsystem/teams/cities/GET.ts @@ -2,21 +2,22 @@ import { Router } from "express"; import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { - app.get('/api/plotsystem/teams/:apikey/cities', function (req, res) { + app.get('/api/plotsystem/teams/:apikey/cities', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) return; - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); return; } + const cities = await buildTeam.getPSCities(); res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(buildTeam.getPSCities())) + res.send(JSON.stringify(cities)) }) } \ No newline at end of file diff --git a/src/routes/plotsystem/teams/countries/GET.ts b/src/routes/plotsystem/teams/countries/GET.ts index 57f14a1..b472f15 100644 --- a/src/routes/plotsystem/teams/countries/GET.ts +++ b/src/routes/plotsystem/teams/countries/GET.ts @@ -3,20 +3,20 @@ import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { - app.get('/api/plotsystem/teams/:apikey/countries', function (req, res) { + app.get('/api/plotsystem/teams/:apikey/countries', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) return; - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); return; } - const map = buildTeam.getPSCountries(); + const map = await buildTeam.getPSCountries(); res.setHeader('Content-Type', 'application/json'); res.send(Object.fromEntries(map)) diff --git a/src/routes/plotsystem/teams/plots/GET.ts b/src/routes/plotsystem/teams/plots/GET.ts index 082d217..2c9e4a2 100644 --- a/src/routes/plotsystem/teams/plots/GET.ts +++ b/src/routes/plotsystem/teams/plots/GET.ts @@ -3,14 +3,14 @@ import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { - app.get('/api/plotsystem/teams/:apikey/plots', function (req, res) { + app.get('/api/plotsystem/teams/:apikey/plots', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) return; - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); diff --git a/src/routes/plotsystem/teams/plots/POST.ts b/src/routes/plotsystem/teams/plots/POST.ts index ba6e6b1..89d07dd 100644 --- a/src/routes/plotsystem/teams/plots/POST.ts +++ b/src/routes/plotsystem/teams/plots/POST.ts @@ -3,13 +3,13 @@ import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { - app.post('/api/plotsystem/teams/:apikey/plots', function (req, res) { + app.post('/api/plotsystem/teams/:apikey/plots', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) return; - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); @@ -52,7 +52,8 @@ export async function initRoutes(app: Router, joi: any, network: Network) { // Validate that the city exists - if(!buildTeam.getPSCities().some(city => city.id == city_project_id)) + const cities = await buildTeam.getPSCities(); + if(!cities.some(city => city.id == city_project_id)) res.status(400).send({success: false, error: 'The city does not exist'}); diff --git a/src/routes/plotsystem/teams/plots/PUT.ts b/src/routes/plotsystem/teams/plots/PUT.ts index b3bb371..2f64d75 100644 --- a/src/routes/plotsystem/teams/plots/PUT.ts +++ b/src/routes/plotsystem/teams/plots/PUT.ts @@ -4,7 +4,7 @@ import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { // A put request to change plot settings of a build team - app.put('/api/plotsystem/teams/:apikey/plots', function (req, res) { + app.put('/api/plotsystem/teams/:apikey/plots', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) @@ -17,7 +17,7 @@ export async function initRoutes(app: Router, joi: any, network: Network) { return; } - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); diff --git a/src/routes/plotsystem/teams/reviews/GET.ts b/src/routes/plotsystem/teams/reviews/GET.ts index 7d24d8f..0354f1b 100644 --- a/src/routes/plotsystem/teams/reviews/GET.ts +++ b/src/routes/plotsystem/teams/reviews/GET.ts @@ -3,24 +3,23 @@ import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { - app.get('/api/plotsystem/teams/:apikey/reviews', function (req, res) { + app.get('/api/plotsystem/teams/:apikey/reviews', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) return; - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); return; } - buildTeam.getPSReviews().then((reviews) => { - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(reviews)) - }) + const reviews = await buildTeam.getPSReviews() + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(reviews)); }) } \ No newline at end of file diff --git a/src/routes/plotsystem/teams/servers/GET.ts b/src/routes/plotsystem/teams/servers/GET.ts index 46d7619..34040ef 100644 --- a/src/routes/plotsystem/teams/servers/GET.ts +++ b/src/routes/plotsystem/teams/servers/GET.ts @@ -3,21 +3,21 @@ import Network from "../../../../struct/core/network.js"; export async function initRoutes(app: Router, joi: any, network: Network) { - app.get('/api/plotsystem/teams/:apikey/servers', function (req, res) { + app.get('/api/plotsystem/teams/:apikey/servers', async function (req, res) { // Validate that the API key is a valid GUID if(!network.validateAPIKey(req, res)) return; - const buildTeam = network.getBuildTeam(req.params.apikey); + const buildTeam = await network.getBuildTeam(req.params.apikey); if(buildTeam == null) { res.status(400).send({ error: 'Build Team not found' }); return; } - const map = buildTeam.getPSServers(); + const map = await buildTeam.getPSServers(); res.setHeader('Content-Type', 'application/json'); res.send(Object.fromEntries(map)) diff --git a/src/struct/core/buildteam.ts b/src/struct/core/buildteam.ts index eca0ca9..06736d0 100644 --- a/src/struct/core/buildteam.ts +++ b/src/struct/core/buildteam.ts @@ -1,20 +1,29 @@ import DatabaseHandler from "../database.js"; - +import Network from "./network.js"; export default class BuildTeam { + private static readonly CITY_UPDATE_INTERVAL: number = 60 * 24; // 24 hours + private static readonly COUNTRY_UPDATE_INTERVAL: number = 60 * 24; // 24 hours + private static readonly SERVER_UPDATE_INTERVAL: number = 60 * 24; // 24 hours + private static readonly FTP_CONFIGURATION_UPDATE_INTERVAL: number = 60 * 24; // 24 hours + + private api_key: string; + private network: Network; private ps_database: DatabaseHandler private buildteamID: string | null; - private cities: Map = new Map() // - private countries: Map = new Map() // Map - private servers: Map = new Map() // Map - private ftp_configuration: Map = new Map(); // Map - constructor(api_key: string, ps_database: DatabaseHandler) { + private ps_cities: Map = new Map() // Map + private ps_countries: Map = new Map() // Map + private ps_servers: Map = new Map() // Map + private ps_ftp_configuration: Map = new Map(); // Map + + constructor(api_key: string, network: Network) { this.api_key = api_key; - this.ps_database = ps_database; + this.network = network; + this.ps_database = network.getPlotSystemDatabase(); this.buildteamID = null; } @@ -24,19 +33,34 @@ export default class BuildTeam { async updateCache(){ if(this.ps_database.settings.debug) console.log("Updating cache for build team: " + this.api_key) + + + if(this.ps_cities != null && this.network.getUpdateCacheTicks() % BuildTeam.CITY_UPDATE_INTERVAL == 0) + this.ps_cities.clear(); + + if(this.ps_countries != null && this.network.getUpdateCacheTicks() % BuildTeam.COUNTRY_UPDATE_INTERVAL == 0) + this.ps_countries.clear(); + + if(this.ps_servers != null && this.network.getUpdateCacheTicks() % BuildTeam.SERVER_UPDATE_INTERVAL == 0) + this.ps_servers.clear(); + + if(this.ps_ftp_configuration != null && this.network.getUpdateCacheTicks() % BuildTeam.FTP_CONFIGURATION_UPDATE_INTERVAL == 0) + this.ps_ftp_configuration.clear(); + } + + async loadBuildTeamData(){ + if(this.ps_database.settings.debug) + console.log("Loading data for build team: " + this.api_key) this.buildteamID = await this.getPSBuildTeamIDFromDatabase(); if(this.buildteamID == undefined || this.buildteamID == null) return; - this.cities.clear(); - this.countries.clear(); - this.servers.clear(); - this.ftp_configuration.clear(); - // Update all countries, cities, servers and ftp configurations const countries = await this.getPSCountriesFromDatabase(); + + // Loop through all countries for(const country of countries){ const cities = await this.getPSCitiesFromDatabase(country.id); const servers = await this.getPSServersFromDatabase(country.id); @@ -45,86 +69,77 @@ export default class BuildTeam { for(const server of servers){ const ftp_configuration = await this.getPSFTPConfigurationFromDatabase(server.id); - this.servers.set(server.id, server); - this.ftp_configuration.set(server.id, ftp_configuration); + this.ps_servers.set(server.id, server); + this.ps_ftp_configuration.set(server.id, ftp_configuration); } - this.cities.set(country.id, cities); - this.countries.set(country.id, country); + this.ps_cities.set(country.id, cities); + this.ps_countries.set(country.id, country); } } + /* ======================================= */ /* PlotSystem */ /* ======================================= */ // Returns a list of cities. If no cities are found, an empty list is returned. - getPSCities(){ - if(this.cities == null || this.cities.size == 0){ - this.updateCache(); - return [] - } + async getPSCities(){ + if(this.ps_cities == null || this.ps_cities.size == 0) + await this.loadBuildTeamData(); const cities = []; - for(const city of this.cities.values()) + for(const city of this.ps_cities.values()) cities.push(...city); return cities; } // Returns a list of cities for the given country id. If the country id is not found, an empty list is returned. - getPSCitiesByCountry(country_id: number){ - if(this.cities == null || this.cities.size == 0){ - this.updateCache(); - return [] - } + async getPSCitiesByCountry(country_id: number){ + if(this.ps_cities == null || this.ps_cities.size == 0) + await this.loadBuildTeamData(); - if(!this.cities.has(country_id)) + if(!this.ps_cities.has(country_id)) return []; - return this.cities.get(country_id); + return this.ps_cities.get(country_id); } // Returns a map of countries with the country id as the key. If no countries are found, an empty map is returned. - getPSCountries(){ - if(this.countries == null || this.countries.size == 0){ - this.updateCache(); - return new Map(); - } + async getPSCountries(){ + if(this.ps_countries == null || this.ps_countries.size == 0) + await this.loadBuildTeamData(); - return this.countries; + return this.ps_countries; } // Returns a map of servers with the server id as the key. If no servers are found, an empty map is returned. - getPSServers(){ - if(this.servers == null || this.servers.size == 0){ - this.updateCache(); - return new Map(); - } - - return this.servers; + async getPSServers(){ + if(this.ps_servers == null || this.ps_servers.size == 0) + await this.loadBuildTeamData(); + + return this.ps_servers; } // Returns a map of ftp configurations with the server id as the key. If no ftp configurations are found, an empty map is returned. - getPSFTPConfiguration(){ - if(this.ftp_configuration == null || this.ftp_configuration.size == 0){ - this.updateCache(); - return new Map(); - } + async getPSFTPConfiguration(){ + if(this.ps_ftp_configuration == null || this.ps_ftp_configuration.size == 0) + await this.loadBuildTeamData(); - return this.ftp_configuration; + return this.ps_ftp_configuration; } // Returns an uncached list of plots of this team. If no plots are found, an empty list is returned. async getPSPlots(){ - if(this.cities == null || this.cities.size == 0) - return []; + if(this.ps_cities == null || this.ps_cities.size == 0) + await this.loadBuildTeamData(); const plots = []; - for(const city of this.getPSCities()){ + for(const city of await this.getPSCities()){ const cityPlots = await this.getPSPlotsByCity(city.id); plots.push(...cityPlots); } @@ -134,10 +149,10 @@ export default class BuildTeam { // Checks if the given plot id is valid for this team. If the plot id is not found, false is returned. async isValidPSPlot(plot_id: number){ - if(this.cities == null || this.cities.size == 0) - return false; + if(this.ps_cities == null || this.ps_cities.size == 0) + await this.loadBuildTeamData(); - for(const city of this.getPSCities()){ + for(const city of await this.getPSCities()){ const cityPlots = await this.getPSPlotsByCity(city.id); if(cityPlots.some((plot: {id: number}) => plot.id == plot_id)) return true; @@ -148,8 +163,8 @@ export default class BuildTeam { // Returns a plot for the given plot id. If the plot id is not found, null is returned. async getPSPlot(plot_id: number){ - if(this.cities == null || this.cities.size == 0) - return null; + if(this.ps_cities == null || this.ps_cities.size == 0) + await this.loadBuildTeamData(); for(const plot of await this.getPSPlots()) if(plot.id == plot_id) @@ -160,7 +175,7 @@ export default class BuildTeam { // Returns an uncached list of plots for the given city id. If the city id is not found, an empty list is returned. async getPSPlotsByCity(city_id: number){ - if(!this.cities.has(city_id)) + if(!this.ps_cities.has(city_id)) return []; return await this.getPSCityPlotsFromDatabase(city_id); @@ -168,7 +183,9 @@ export default class BuildTeam { // Creates a new plot for the given city id. If the city id is not found, false is returned. async createPSPlot(city_project_id: number, difficulty_id: number, mc_coordinates: [number, number, number], outline: any, create_player: string, version: string){ - if(!this.getPSCities().some(city => city.id == city_project_id)) + const cities = await this.getPSCities(); + + if(!cities.some(city => city.id == city_project_id)) return false; return await this.createPSPlotInDatabase(city_project_id, difficulty_id, mc_coordinates, outline, create_player, version); @@ -184,11 +201,11 @@ export default class BuildTeam { // Returns an uncached list of reviews. async getPSReviews(){ - if(this.cities == null || this.cities.size == 0) - return []; + if(this.ps_cities == null || this.ps_cities.size == 0) + await this.loadBuildTeamData(); const reviews = []; - for(const city of this.getPSCities()){ + for(const city of await this.getPSCities()){ const cityReviews = await this.getPSReviewsByCity(city.id); reviews.push(...cityReviews); } @@ -198,7 +215,7 @@ export default class BuildTeam { // Returns an uncached list of plots for the given city id. If the city id is not found, an empty list is returned. async getPSReviewsByCity(city_id: number){ - if(!this.cities.has(city_id)) + if(!this.ps_cities.has(city_id)) return []; return await this.getPSCityReviewsFromDatabase(city_id); diff --git a/src/struct/core/network.ts b/src/struct/core/network.ts index fefcf67..8066ca3 100644 --- a/src/struct/core/network.ts +++ b/src/struct/core/network.ts @@ -6,6 +6,8 @@ import express from "express"; import joi from "joi"; export default class Network { + private static readonly API_KEY_UPDATE_INTERVAL: number = 10; // 10 minutes + private plotsystem_database: DatabaseHandler; private network_database: DatabaseHandler; @@ -13,6 +15,8 @@ export default class Network { private buildTeams = new Map(); private plotSystem: PlotSystem; + private update_cache_ticks: number = 0; + constructor(plotsystem_database: DatabaseHandler, network_database: DatabaseHandler) { this.plotsystem_database = plotsystem_database; this.network_database = network_database; @@ -21,7 +25,10 @@ export default class Network { } async updateCache(isStarting: boolean = false) { - this.api_keys = await this.getAPIKeysFromDatabase(); + this.update_cache_ticks++; + + if(this.api_keys == null || this.update_cache_ticks % Network.API_KEY_UPDATE_INTERVAL == 0) + this.api_keys = await this.getAPIKeysFromDatabase(); let bar = null; if (isStarting == true) { @@ -46,14 +53,23 @@ export default class Network { this.plotSystem.updateCache(); for (const apiKey of this?.api_keys?.values() ?? []) { - const buildTeam = this.getBuildTeam(apiKey); + const buildTeam = await this.getBuildTeam(apiKey); if (buildTeam == null) continue; - await buildTeam.updateCache(); + buildTeam.updateCache(); if (isStarting == true) bar?.tick(); } + + + if(this.update_cache_ticks >= Number.MAX_SAFE_INTEGER - 100) + this.update_cache_ticks = 0; + } + + + getUpdateCacheTicks(): number { + return this.update_cache_ticks; } getAPIKeys(): string[] { @@ -77,7 +93,7 @@ export default class Network { return this.plotsystem_database; } - getBuildTeam(api_key: string): BuildTeam | null { + async getBuildTeam(api_key: string): Promise { const api_keys = this.getAPIKeys(); // Validate that the API key exists in the plot system database @@ -87,7 +103,7 @@ export default class Network { if (this.buildTeams.has(api_key)) return this.buildTeams.get(api_key); // Create a new build team and add it to the cache - const buildTeam = new BuildTeam(api_key, this.plotsystem_database); + const buildTeam = new BuildTeam(api_key, this); this.buildTeams.set(api_key, buildTeam); return buildTeam; @@ -110,7 +126,7 @@ export default class Network { //Validate that the API key exists in the plot system database const api_keys = this.getAPIKeys(); - console.log(req.params); + if (!api_keys.includes(req.params.apikey)) { res.status(401).send({ success: false, error: "Invalid API key" }); return false; @@ -122,8 +138,8 @@ export default class Network { // Get values from database async getAPIKeysFromDatabase() { - const SQL = "SELECT * FROM plotsystem_api_keys"; - const result = await this.plotsystem_database.query(SQL); // result: [{"id":1,"api_key":"super_cool_api_key","created_at":"2022-06-23T18:00:09.000Z"}] - return result.map((row: { api_key: string }) => row.api_key); // result: ["super_cool_api_key"] + const SQL = "SELECT APIKey FROM BuildTeams"; + const result = await this.network_database.query(SQL); // result: [{"APIKey":"super_cool_api_key"}] + return result.map((row: { APIKey: string }) => row.APIKey); // result: ["super_cool_api_key"] } } diff --git a/src/struct/core/plotsystem.ts b/src/struct/core/plotsystem.ts index 8575cbf..5019517 100644 --- a/src/struct/core/plotsystem.ts +++ b/src/struct/core/plotsystem.ts @@ -2,19 +2,33 @@ import DatabaseHandler from "../database.js"; import Network from "./network.js"; export default class PlotSystem { + + private static readonly BUILDERS_UPDATE_INTERVAL: number = 60 * 1; // 1 hour + private static readonly DIFFICULTIES_UPDATE_INTERVAL: number = 60 * 24; // 24 hours + + + private network: Network; private plotsystem_database: DatabaseHandler; private builders: any[] | null = null; private difficulties: any[] | null = null; + constructor(network: Network) { this.plotsystem_database = network.getPlotSystemDatabase(); + this.network = network; } + // Updates the cache for the build team to make sure the cache is up to date (Called once a minute) async updateCache(isStarting: boolean = false) { - this.builders = await this.getBuildersFromDatabase(); - this.difficulties = await this.getDifficultiesFromDatabase(); + if(this.builders == null || this.network.getUpdateCacheTicks() % PlotSystem.BUILDERS_UPDATE_INTERVAL == 0) + this.builders = await this.getBuildersFromDatabase(); + + if(this.builders == null || this.network.getUpdateCacheTicks() % PlotSystem.DIFFICULTIES_UPDATE_INTERVAL == 0) + this.difficulties = await this.getDifficultiesFromDatabase(); } + + getPSBuilders() { if (this.builders == null) { this.updateCache();