Browse Source

- Initial commit of old work from https://sinistea.pkmn.cloud/Quildra/OriginDex

master
Dan 1 year ago
parent
commit
d3e1dbf118
  1. 1
      .gitignore
  2. 3287
      package-lock.json
  3. 33
      package.json
  4. 423
      src/server.ts
  5. 14
      tsconfig.json

1
.gitignore

@ -129,3 +129,4 @@ dist
# Local History for Visual Studio Code
.history/
user_data.db

3287
package-lock.json

File diff suppressed because it is too large

33
package.json

@ -0,0 +1,33 @@
{
"name": "origin-dex-api",
"version": "1.0.0",
"main": "dist/server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node dist/server.js",
"dev": "ts-node-dev src/server.ts",
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.7",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"jsonwebtoken": "^9.0.2",
"sqlite": "^5.0.1",
"sqlite3": "^5.1.6"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/node": "^22.8.6",
"ts-node-dev": "^2.0.0",
"typescript": "^5.6.3"
}
}

423
src/server.ts

@ -0,0 +1,423 @@
import dotenv from 'dotenv';
// Add this line before other imports
dotenv.config();
import express from 'express';
import { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import fs from 'fs/promises';
import path from 'path';
const app = express();
const port = process.env.PORT || 5000;
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
throw new Error('JWT_SECRET must be defined');
}
interface AuthRequest extends Request {
user?: any;
}
// Middleware
app.use(cors());
app.use(express.json());
const userDbPromise = open({
filename: './user_data.db', // This will be created in the api folder
driver: sqlite3.Database
});
// Initialize users table
async function initializeDb() {
const db = await userDbPromise;
await db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS caught_pokemon (
user_id INTEGER,
pfic TEXT,
caught_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, pfic),
FOREIGN KEY (user_id) REFERENCES users(id)
);
`);
}
initializeDb().catch(console.error);
// Update the auth middleware
const authenticateToken = (
req: AuthRequest,
res: Response,
next: NextFunction
): void => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
res.status(401).json({ error: 'Authentication required' });
return;
}
try {
const user = jwt.verify(token, JWT_SECRET as jwt.Secret);
req.user = user;
next();
} catch (err) {
res.status(403).json({ error: 'Invalid token' });
return;
}
};
// Update the route handlers
app.post('/api/auth/register', (req: Request, res: Response) => {
const { username, password } = req.body;
userDbPromise.then(async (db) => {
try {
const existingUser = await db.get('SELECT id FROM users WHERE username = ?', username);
if (existingUser) {
return res.status(400).json({ error: 'Username already exists' });
}
const hashedPassword = await bcrypt.hash(password, 10);
await db.run(
'INSERT INTO users (username, password) VALUES (?, ?)',
[username, hashedPassword]
);
res.status(201).json({ message: 'User registered successfully' });
} catch (err) {
console.error('Registration error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
});
app.post('/api/auth/login', (req: Request, res: Response) => {
const { username, password } = req.body;
userDbPromise.then(async (db) => {
try {
const user = await db.get('SELECT * FROM users WHERE username = ?', username);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign(
{ id: user.id, username: user.username },
JWT_SECRET as jwt.Secret,
{ expiresIn: '24h' }
);
res.json({
id: user.id,
username: user.username,
token
});
} catch (err) {
console.error('Login error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
});
// Database connection
const dbPromise = open({
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 processedData = processPokemonData(pokemon);
res.json(processedData);
} catch (err) {
console.error('Error fetching pokemon:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
app.get('/api/pokemon/:pfic/details', async (req, res) => {
try {
const db = await dbPromise;
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 = ?
`, 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);
res.json({ ...details, encounters });
} catch (err) {
console.error('Error fetching pokemon details:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
app.get('/api/plan', authenticateToken, async (req: AuthRequest, res: Response) => {
try {
// Read the efficiency plan file
const planData = await fs.readFile(
path.join(__dirname, '../../efficiency_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
});
// Get user's caught Pokemon
const userDb = await userDbPromise;
const caughtPokemon = await userDb.all(
'SELECT pfic FROM caught_pokemon WHERE user_id = ?',
[req.user.id]
);
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];
}
// 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);
}
return ['Evolution'];
}
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}`);
}
// 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}`);
}
// Add evolution methods
for (const evolution of pokemon.evolve_to) {
const methods = await getEvolutionMethods(pokemon.pfic, evolution.pfic);
evolution.method = methods.join(' → ');
}
}
// 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}`);
}
}
// 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})`);
}
}
}
}
}
}
await db.close();
res.json(efficiencyPlan);
} catch (err) {
console.error('Error loading efficiency plan:', err);
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) => {
try {
const caught = await db.all(
'SELECT pfic FROM caught_pokemon WHERE user_id = ?',
req.user.id
);
res.json(caught.map(c => c.pfic));
} catch (err) {
console.error('Error fetching caught pokemon:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
});
app.post('/api/pokemon/caught/:pfic', authenticateToken, (req: AuthRequest, res: Response) => {
const { pfic } = req.params;
void userDbPromise.then(async (db) => {
try {
const existing = await db.get(
'SELECT 1 FROM caught_pokemon WHERE user_id = ? AND pfic = ?',
[req.user.id, pfic]
);
if (existing) {
await db.run(
'DELETE FROM caught_pokemon WHERE user_id = ? AND pfic = ?',
[req.user.id, pfic]
);
res.json({ status: 'released' });
} else {
await db.run(
'INSERT INTO caught_pokemon (user_id, pfic) VALUES (?, ?)',
[req.user.id, pfic]
);
res.json({ status: 'caught' });
}
console.log(`Caught ${pfic}`);
} catch (err) {
console.error('Error updating caught status:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});

14
tsconfig.json

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Loading…
Cancel
Save