|
|
|
@ -1,15 +1,141 @@ |
|
|
|
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
|
|
|
|
@ -84,8 +210,8 @@ app.get('/api/pokemon', async (req, res) => { |
|
|
|
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 |
|
|
|
JOIN form_marks fm ON pf.PFIC = fm.pfic |
|
|
|
JOIN marks m ON fm.mark_id = m.id |
|
|
|
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 |
|
|
|
`);
|
|
|
|
@ -128,14 +254,125 @@ app.get('/api/pokemon/:pfic/details', async (req, res) => { |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
app.get('/api/pokemon/caught', async (req, res) => { |
|
|
|
// This will need to be implemented with your user authentication system
|
|
|
|
res.json([]); |
|
|
|
app.get('/api/plan', async (req: Request, 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 |
|
|
|
}); |
|
|
|
|
|
|
|
// 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']; |
|
|
|
} |
|
|
|
|
|
|
|
// Enhance the plan with evolution methods
|
|
|
|
for (const game of efficiencyPlan) { |
|
|
|
for (const pokemon of game.pokemon) { |
|
|
|
if (pokemon.evolve_to) { |
|
|
|
for (const evolution of pokemon.evolve_to) { |
|
|
|
const methods = await getEvolutionMethods(pokemon.pfic, evolution.pfic); |
|
|
|
evolution.method = methods.join(' → '); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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', async (req, res) => { |
|
|
|
// This will need to be implemented with your user authentication system
|
|
|
|
res.json({ status: 'caught' }); |
|
|
|
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' }); |
|
|
|
} |
|
|
|
} catch (err) { |
|
|
|
console.error('Error updating caught status:', err); |
|
|
|
res.status(500).json({ error: 'Internal server error' }); |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
app.listen(port, () => { |
|
|
|
|