Fix: Ensure all skills are tracked as files, not submodules
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import path from 'path';
|
||||
|
||||
const dbPath = path.join(__dirname, '../../todos.db');
|
||||
|
||||
// Create database connection
|
||||
let db: Database.Database | null = null;
|
||||
|
||||
export function getDatabase(): Database.Database {
|
||||
if (!db) {
|
||||
db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
console.log(`Connected to SQLite database at ${dbPath}`);
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export function closeDatabase(): void {
|
||||
if (db) {
|
||||
db.close();
|
||||
db = null;
|
||||
console.log('Database connection closed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import sqlite3 from 'sqlite3';
|
||||
import path from 'path';
|
||||
|
||||
const dbPath = path.join(__dirname, '../../todos.db');
|
||||
|
||||
const db = new sqlite3.Database(dbPath, (err: Error | null) => {
|
||||
if (err) {
|
||||
console.error('Database connection error:', err);
|
||||
} else {
|
||||
console.log('Connected to SQLite database');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize database schema
|
||||
export const initDatabase = (): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
completed BOOLEAN DEFAULT 0,
|
||||
createdAt TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`, (err: Error | null) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
console.log('Database schema initialized');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default db;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { getDatabase, closeDatabase } from './database';
|
||||
export { runMigrations, initializeDatabase } from './migrations';
|
||||
@@ -0,0 +1,31 @@
|
||||
import { getDatabase } from './database';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const schemaPath = path.join(__dirname, './schema.sql');
|
||||
|
||||
export function runMigrations(): void {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const schema = fs.readFileSync(schemaPath, 'utf-8');
|
||||
|
||||
// Execute the schema SQL
|
||||
db.exec(schema);
|
||||
|
||||
console.log('Database migrations completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Error running migrations:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeDatabase(): void {
|
||||
try {
|
||||
runMigrations();
|
||||
console.log('Database initialized and ready for use');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize database:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
completed INTEGER DEFAULT 0,
|
||||
createdAt TEXT,
|
||||
updatedAt TEXT
|
||||
);
|
||||
@@ -0,0 +1,44 @@
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import { initializeDatabase, closeDatabase } from './db';
|
||||
import todosRouter from './routes/todos';
|
||||
|
||||
const app: Express = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Initialize database on startup
|
||||
try {
|
||||
initializeDatabase();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize database:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Routes
|
||||
app.use('/api', todosRouter);
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (_req: Request, res: Response) => {
|
||||
res.json({ status: 'ok', message: 'Backend server is running' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Shutting down gracefully...');
|
||||
closeDatabase();
|
||||
server.close(() => {
|
||||
console.log('Server closed');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,155 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import db from '../db/db';
|
||||
import { ApiResponse, Todo } from '../types/index';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// GET /api/todos - Retrieve all todos
|
||||
router.get('/todos', (_req: Request, res: Response): void => {
|
||||
db.all('SELECT * FROM todos ORDER BY createdAt DESC', (err: any, rows: Todo[]) => {
|
||||
if (err) {
|
||||
const errorResponse: ApiResponse<null> = {
|
||||
success: false,
|
||||
error: 'Database error',
|
||||
};
|
||||
res.status(500).json(errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
const successResponse: ApiResponse<Todo[]> = {
|
||||
success: true,
|
||||
data: rows || [],
|
||||
};
|
||||
res.json(successResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// POST /api/todos - Create new todo
|
||||
router.post('/todos', (req: Request, res: Response): void => {
|
||||
const { title } = req.body;
|
||||
|
||||
// Validation
|
||||
if (!title || typeof title !== 'string' || title.trim() === '') {
|
||||
res.status(400).json({ error: 'Title is required and must be a non-empty string' });
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedTitle = title.trim();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
db.run(
|
||||
'INSERT INTO todos (title, completed, createdAt, updatedAt) VALUES (?, ?, ?, ?)',
|
||||
[trimmedTitle, 0, now, now],
|
||||
function(this: any, err: Error | null) {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
// Return created todo
|
||||
db.get('SELECT * FROM todos WHERE id = ?', [this.lastID], (err: any, row: Todo) => {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
const successResponse: ApiResponse<Todo> = {
|
||||
success: true,
|
||||
data: row,
|
||||
};
|
||||
res.status(201).json(successResponse);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// PATCH /api/todos/:id - Update todo completion status
|
||||
router.patch('/todos/:id', (req: Request, res: Response): void => {
|
||||
const { id } = req.params;
|
||||
const { completed } = req.body;
|
||||
|
||||
// Validation
|
||||
if (typeof completed !== 'boolean') {
|
||||
res.status(400).json({ error: 'Completed must be a boolean value' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if todo exists
|
||||
db.get('SELECT * FROM todos WHERE id = ?', [id], (err: any, row: Todo) => {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
if (!row) {
|
||||
res.status(404).json({ error: 'Todo not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Update todo
|
||||
db.run(
|
||||
'UPDATE todos SET completed = ?, updatedAt = ? WHERE id = ?',
|
||||
[completed ? 1 : 0, now, id],
|
||||
function(err: Error | null) {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
// Return updated todo
|
||||
db.get('SELECT * FROM todos WHERE id = ?', [id], (err: any, updatedRow: Todo) => {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
const successResponse: ApiResponse<Todo> = {
|
||||
success: true,
|
||||
data: updatedRow,
|
||||
};
|
||||
res.json(successResponse);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// DELETE /api/todos/:id - Delete todo by id
|
||||
router.delete('/todos/:id', (req: Request, res: Response): void => {
|
||||
const { id } = req.params;
|
||||
|
||||
// Validation - check if id is a valid number
|
||||
if (!id || isNaN(Number(id))) {
|
||||
res.status(400).json({ error: 'Invalid id parameter' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if todo exists
|
||||
db.get('SELECT * FROM todos WHERE id = ?', [id], (err: any, row: Todo) => {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
if (!row) {
|
||||
res.status(404).json({ error: 'Todo not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete todo
|
||||
db.run(
|
||||
'DELETE FROM todos WHERE id = ?',
|
||||
[id],
|
||||
function(err: Error | null) {
|
||||
if (err) {
|
||||
res.status(500).json({ error: 'Database error', details: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ message: 'Todo deleted successfully' });
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,35 @@
|
||||
// Todo item types
|
||||
export interface Todo {
|
||||
id: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
completed: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// API response types
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Request body types
|
||||
export interface CreateTodoRequest {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateTodoRequest {
|
||||
title?: string;
|
||||
description?: string;
|
||||
completed?: boolean;
|
||||
}
|
||||
|
||||
// Database types
|
||||
export interface DatabaseConfig {
|
||||
path: string;
|
||||
readonly?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user