Browse Source

Merge pull request 'new-db-structure-convertion' (#1) from new-db-structure-convertion into master

Reviewed-on: https://sinistea.pkmn.cloud/Quildra/OriginDex-API/pulls/1
master
Quildra 12 months ago
parent
commit
10c3b51636
  1. 21
      .vscode/launch.json
  2. 1
      package-lock.json
  3. 6839
      plan.json
  4. 1
      pokemon_evolution_graph.json
  5. BIN
      pokemon_forms.db
  6. 492
      src/server.ts
  7. 2
      tsconfig.json

21
.vscode/launch.json

@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug with ts-node-dev",
"runtimeExecutable": "npx",
"runtimeArgs": ["ts-node-dev", "--inspect", "--respawn", "src/server.ts"],
"skipFiles": ["<node_internals>/**"],
"env": {
"NODE_ENV": "development"
},
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}

1
package-lock.json

@ -3048,7 +3048,6 @@
"resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
"integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.1",
"dynamic-dedupe": "^0.3.0",

6839
plan.json

File diff suppressed because it is too large

1
pokemon_evolution_graph.json

File diff suppressed because one or more lines are too long

BIN
pokemon_forms.db

Binary file not shown.

492
src/server.ts

@ -4,8 +4,8 @@ dotenv.config();
import express from 'express';
import { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import { open, Database } from 'sqlite';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import fs from 'fs/promises';
@ -31,6 +31,66 @@ const userDbPromise = open({
driver: sqlite3.Database
});
interface Node {
id: string;
[key: string]: any;
}
interface Link {
source: string;
target: string;
method: string;
[key: string]: any;
}
interface Graph {
nodes: Node[];
links: Link[];
}
const graph_file_path = "./pokemon_evolution_graph.json"
const loadGraph = async (): Promise<Graph> => {
const jsonData = await fs.readFile(graph_file_path, 'utf-8');
return JSON.parse(jsonData);
};
async function findNodeEdges(nodeId: string): Promise<{ node: Node; edges: Link[] } | null> {
const graph = await loadGraph();
const node = graph.nodes.find((n) => n.id === nodeId);
if (!node) {
console.log(`Node with ID ${nodeId} not found.`);
return null;
}
console.log(`Node found: ${node.id}`);
const edges = graph.links.filter((link) => link.source === nodeId || link.target === nodeId);
edges.forEach((edge) => {
const relatedNode = edge.source === nodeId ? edge.target : edge.source;
console.log(`Edge found between ${nodeId} and ${relatedNode} with method: ${edge.method}`);
});
return { node, edges };
}
async function findNodeBackwardEdges(nodeId: string): Promise<{ node: Node; edges: Link[] } | null> {
const graph = await loadGraph();
const node = graph.nodes.find((n) => n.id === nodeId);
if (!node) {
//console.log(`Node with ID ${nodeId} not found.`);
return null;
}
//console.log(`Node found: ${node.id}`);
const edges = graph.links.filter((link) => link.target === nodeId);
edges.forEach((edge) => {
//console.log(`Backward edge found from ${edge.source} to ${nodeId} with method: ${edge.method}`);
});
return { node, edges };
}
// Initialize users table
async function initializeDb() {
const db = await userDbPromise;
@ -138,85 +198,31 @@ app.post('/api/auth/login', (req: Request, res: Response) => {
// Database connection
const dbPromise = open({
filename: '../pokemon_forms.db', // Adjust path to your database file
filename: './pokemon_forms.db', // Adjust path to your database file
driver: sqlite3.Database
});
function processPokemonData(pokemonData: any[]): any[][] {
const pokemonList: any[][] = [];
let current_group: any[] = [];
let current_dex_number = 0;
let current_generation = 0;
let pokemon_forms: any[] = [];
for (const pokemon of pokemonData) {
if (pokemon.national_dex !== current_dex_number) {
if (pokemon_forms.length > 0) {
for (const form of pokemon_forms) {
current_group.push(form);
if (current_group.length === 30) {
pokemonList.push([...current_group]);
current_group = [];
}
}
pokemon_forms = [];
}
current_dex_number = pokemon.national_dex;
if (!pokemon.form_name) {
if (current_generation === null || pokemon.generation !== current_generation) {
if (current_group.length > 0) {
while (current_group.length < 30) {
current_group.push(null);
}
pokemonList.push([...current_group]);
current_group = [];
}
current_generation = pokemon.generation || 0;
}
}
}
pokemon_forms.push(pokemon);
}
// Handle remaining pokemon forms
for (const form of pokemon_forms) {
current_group.push(form);
if (current_group.length === 30) {
pokemonList.push([...current_group]);
current_group = [];
}
}
// Handle the last group
if (current_group.length > 0) {
while (current_group.length < 30) {
current_group.push(null);
}
pokemonList.push([...current_group]);
}
return pokemonList;
}
// Routes
app.get('/api/pokemon', async (req, res) => {
try {
const db = await dbPromise;
const pokemon = await db.all(`
SELECT
pf.national_dex, pf.name, pf.form_name, pf.PFIC, pf.generation,
ps.storable_in_home, m.icon_path, m.name as mark_name
FROM pokemon_forms pf
JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC
LEFT JOIN form_marks fm ON pf.PFIC = fm.pfic
LEFT JOIN marks m ON fm.mark_id = m.id
WHERE ps.storable_in_home = 1
ORDER BY pf.PFIC
const pokemon_data = await db.all(`
SELECT *
FROM pokemon_forms
WHERE JSON_EXTRACT(data, '$.storable_in_home') == true
GROUP BY json_extract(data, '$.sprite_url')
ORDER BY PFIC
`);
const processedData = processPokemonData(pokemon);
var processedData: any[] = []
for (const db_pokemon of pokemon_data) {
const data = JSON.parse(db_pokemon.data)
const pokemon = {
"pfic": db_pokemon.PFIC,
"data": data
}
processedData.push(pokemon)
}
res.json(processedData);
} catch (err) {
console.error('Error fetching pokemon:', err);
@ -230,46 +236,113 @@ app.get('/api/pokemon/:pfic/details', async (req, res) => {
const { pfic } = req.params;
const details = await db.get(`
SELECT pf.name, pf.form_name, pf.national_dex, pf.generation,
ps.storable_in_home, pf.is_baby_form
FROM pokemon_forms pf
LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC
WHERE pf.PFIC = ?
SELECT *
FROM pokemon_forms
WHERE PFIC = ?
`, pfic);
const encounters = await db.all(`
SELECT g.name as game_name, e.location, e.day, e.time,
e.dual_slot, e.static_encounter_count, e.static_encounter,
e.extra_text, e.stars, e.rods, e.fishing
FROM encounters e
JOIN games g ON e.game_id = g.id
WHERE e.pfic = ?
ORDER BY g.name, e.location
`, pfic);
const data = JSON.parse(details.data)
const pokemon = {
"pfic": details.PFIC,
"data": data
}
let cluster = await findNodeBackwardEdges(details.PFIC)
if (cluster && cluster?.edges.length > 0) {
pokemon.data.evolution_method = cluster?.edges[0].method
}
res.json(pokemon);
} catch (err) {
console.error('Error fetching pokemon details:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
res.json({ ...details, encounters });
app.post('/api/pokemon/batch/details', async (req, res) => {
try {
const db = await dbPromise;
const { pfics } = req.body;
if (!Array.isArray(pfics) || pfics.length === 0) {
res.json({ error: 'Invalid input, expected an array of PFICs.' });
return
}
const placeholders = pfics.map(() => '?').join(',');
const detailsList = await db.all(
`SELECT *
FROM pokemon_forms
WHERE PFIC IN (${placeholders})`,
pfics
);
const pokemonList = await Promise.all(detailsList.map(async (details) => {
const data = JSON.parse(details.data);
const pokemon = {
"pfic": details.PFIC,
"data": data
};
let cluster = await findNodeBackwardEdges(details.PFIC);
if (cluster && cluster.edges.length > 0) {
pokemon.data.evolution_method = cluster.edges[0].method;
}
return pokemon;
}));
res.json(pokemonList);
} catch (err) {
console.error('Error fetching pokemon details:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
interface GamePlan {
game_name: string;
pokemon: Record<string, PokemonFamilyEntry>;
}
interface PokemonFamilyEntry {
family_pfic?: string;
representative: string;
catch_count: number;
caught_count: number;
evolve_to: string[];
breed_for: string[];
Any?: number;
Male?: number;
Female?: number;
evolve_to_augmented?: PokemonEntry[]
breed_for_augmented?: PokemonEntry[]
}
interface PokemonEntry {
pfic: string
name: string
}
const getDbConnection = async (): Promise<Database<sqlite3.Database, sqlite3.Statement>> => {
return open({
filename: './pokemon_forms.db', // Adjust path to your database file
driver: sqlite3.Database
});
};
app.get('/api/plan', authenticateToken, async (req: AuthRequest, res: Response) => {
let db: Database<sqlite3.Database, sqlite3.Statement> | null = null;
try {
// Get the Pokemon database connection
db = await getDbConnection();
// Read the efficiency plan file
const planData = await fs.readFile(
path.join(__dirname, '../../efficiency_plan.json'),
path.join(__dirname, '../plan.json'),
'utf-8'
);
const efficiencyPlan = JSON.parse(planData);
// Get the Pokemon database connection
const db = await open({
filename: '../pokemon_forms.db',
driver: sqlite3.Database
});
const efficiencyPlan: GamePlan[] = JSON.parse(planData);
// Get user's caught Pokemon
const userDb = await userDbPromise;
const caughtPokemon = await userDb.all(
'SELECT pfic FROM caught_pokemon WHERE user_id = ?',
@ -277,88 +350,171 @@ app.get('/api/plan', authenticateToken, async (req: AuthRequest, res: Response)
);
const caughtPfics = new Set(caughtPokemon.map(p => p.pfic));
// Helper function to get evolution methods
async function getEvolutionMethods(fromPfic: string, toPfic: string) {
// Try direct evolution first
const direct = await db.get(`
SELECT method, to_pfic
FROM evolution_chains
WHERE from_pfic = ? AND to_pfic = ?
`, [fromPfic, toPfic]);
if (direct) {
return [direct.method];
}
// Loop through each game plan
for (let i = 0; i < efficiencyPlan.length; i++) {
const game_plan = efficiencyPlan[i];
for (const key in game_plan.pokemon) {
if (Object.hasOwnProperty.call(game_plan.pokemon, key)) {
const pokemonFamily: PokemonFamilyEntry = game_plan.pokemon[key];
pokemonFamily.family_pfic = key;
pokemonFamily.caught_count = 0;
// Merge evolve_to into pfics array
const pfics: string[] = pokemonFamily.evolve_to;
// Loop through pfics to get details from the database
for (let j = 0; j < pfics.length; j++) {
const pkmn: string = pfics[j];
try {
const details = await db.get<{ PFIC: string; data: string }>(
`SELECT * FROM pokemon_forms WHERE PFIC = ?`,
pkmn
);
if (!details) {
console.log("Details not found for PFIC:", pkmn);
continue;
}
const data = JSON.parse(details.data);
const pokemon = {
pfic: details.PFIC,
data: data,
};
if (!pokemonFamily.evolve_to_augmented) {
pokemonFamily.evolve_to_augmented = [];
}
const entry = {
pfic: key,
name: pokemon.data.name,
};
pokemonFamily.evolve_to_augmented.push(entry);
if (caughtPfics.has(pkmn)) {
pokemonFamily.caught_count += 1;
}
} catch (err) {
console.error(`Error fetching details for PFIC ${pkmn}:`, err);
}
}
const breed_pfics: string[] = pokemonFamily.breed_for
for (let j = 0; j < breed_pfics.length; j++) {
const pkmn: string = breed_pfics[j];
try {
const details = await db.get<{ PFIC: string; data: string }>(
`SELECT * FROM pokemon_forms WHERE PFIC = ?`,
pkmn
);
if (!details) {
console.log("Details not found for PFIC:", pkmn);
continue;
}
const data = JSON.parse(details.data);
const pokemon = {
pfic: details.PFIC,
data: data,
};
if (!pokemonFamily.breed_for_augmented) {
pokemonFamily.breed_for_augmented = [];
}
const entry = {
pfic: key,
name: pokemon.data.name,
};
pokemonFamily.breed_for_augmented.push(entry);
// Try indirect evolution path
const methods = await db.all(`
WITH RECURSIVE evolution_path AS (
SELECT from_pfic, to_pfic, method, 1 as depth
FROM evolution_chains
WHERE from_pfic = ?
UNION ALL
SELECT e.from_pfic, e.to_pfic, e.method, ep.depth + 1
FROM evolution_chains e
JOIN evolution_path ep ON e.from_pfic = ep.to_pfic
WHERE ep.depth < 3
)
SELECT method
FROM evolution_path
WHERE to_pfic = ?
ORDER BY depth;
`, [fromPfic, toPfic]);
if (methods && methods.length > 0) {
return methods.map(m => m.method);
if (caughtPfics.has(pkmn)) {
pokemonFamily.caught_count += 1;
}
} catch (err) {
console.error(`Error fetching details for PFIC ${pkmn}:`, err);
}
}
}
}
}
res.json(efficiencyPlan);
return ['Evolution'];
} catch (err) {
console.error('Error loading efficiency plan:', err);
res.status(500).json({ error: 'Internal server error' });
} finally {
if (db) {
await db.close();
}
}
});
const debug_pfic = "0010-01-000-0";
// Enhance the plan with evolution methods and account for caught Pokemon
for (const game of efficiencyPlan) {
for (const pokemon of game.pokemon) {
// Set initial catch count
pokemon.catch_count = 1;
if (pokemon.pfic === debug_pfic) {
console.log(`pokemon: ${pokemon.name} - ${pokemon.catch_count}`);
/*
app.get('/api/plan', authenticateToken, async (req: AuthRequest, res: Response) => {
try {
// Get the Pokemon database connection
const db = await dbPromise;
// Read the efficiency plan file
const planData = await fs.readFile(
path.join(__dirname, '../plan.json'),
'utf-8'
);
const efficiencyPlan: GamePlan[] = JSON.parse(planData);
for (let game_plan of efficiencyPlan){
for (const key in game_plan["pokemon"]) {
//console.log(key)
game_plan["pokemon"][key]["family_pfic"] = key;
let pfics = []
//console.log(game_plan["pokemon"][key])
for (let pkmn of game_plan["pokemon"][key]["evolve_to"]) {
pfics.push(pkmn)
}
for (let pkmn of game_plan["pokemon"][key]["evolve_to"]) {
pfics.push(pkmn)
}
// Add evolution targets to catch count
if (pokemon.evolve_to) {
pokemon.catch_count += pokemon.evolve_to.length;
if (pokemon.pfic === debug_pfic) {
console.log(`pokemon: ${pokemon.name} - ${pokemon.catch_count}`);
for (let pkmn of pfics) {
console.log(pkmn)
const details = await db.get(`
SELECT *
FROM pokemon_forms
WHERE PFIC = ?
`, pkmn);
if(!details) {
console.log("oh noes")
continue;
}
// Add evolution methods
for (const evolution of pokemon.evolve_to) {
const methods = await getEvolutionMethods(pokemon.pfic, evolution.pfic);
evolution.method = methods.join(' → ');
const data = JSON.parse(details.data)
const pokemon = {
"pfic": details.PFIC,
"data": data
}
}
// Reduce catch count for already caught Pokemon
if (caughtPfics.has(pokemon.pfic)) {
pokemon.catch_count = Math.max(0, pokemon.catch_count - 1);
if (pokemon.pfic === debug_pfic) {
console.log(`B pokemon: ${pokemon.name} - ${pokemon.catch_count}`);
if (!game_plan["pokemon"][key].evolve_to_augmented) {
game_plan["pokemon"][key].evolve_to_augmented = []
}
}
// Check evolution targets
if (pokemon.evolve_to) {
for (const evolution of pokemon.evolve_to) {
if (caughtPfics.has(evolution.pfic)) {
pokemon.catch_count = Math.max(0, pokemon.catch_count - 1);
if (pokemon.pfic === debug_pfic) {
console.log(`C pokemon: ${pokemon.name} - ${pokemon.catch_count} (${evolution.pfic})`);
}
}
let entry = {
pfic: key,
name: pokemon.data.name
}
game_plan["pokemon"][key].evolve_to_augmented.push(entry)
}
}
}
@ -371,7 +527,7 @@ app.get('/api/plan', authenticateToken, async (req: AuthRequest, res: Response)
res.status(500).json({ error: 'Internal server error' });
}
});
*/
// Update the caught Pokemon routes
app.get('/api/pokemon/caught', authenticateToken, (req: AuthRequest, res: Response) => {
void userDbPromise.then(async (db) => {

2
tsconfig.json

@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,

Loading…
Cancel
Save