Deploy to production

This commit is contained in:
AI Bot
2026-07-30 12:49:29 +05:30
parent 0d9c9eaadd
commit 07a1e8f37f
30 changed files with 6363 additions and 4887 deletions
+7
View File
@@ -0,0 +1,7 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3005
CMD ["npm", "start"]
+70
View File
@@ -0,0 +1,70 @@
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const fs = require('fs');
const dbPath = path.join(__dirname, 'data', 'database.sqlite');
const dataDir = path.dirname(dbPath);
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
const db = new sqlite3.Database(dbPath, (err) => {
if (err) {
console.error('Error opening database', err.message);
} else {
console.log('Connected to the SQLite database.');
db.run(`CREATE TABLE IF NOT EXISTS profiles (
id TEXT PRIMARY KEY,
email TEXT UNIQUE,
password_hash TEXT,
role TEXT DEFAULT 'user',
api_credits INTEGER DEFAULT 10,
storage_limit_mb INTEGER DEFAULT 500,
is_active INTEGER DEFAULT 0
)`, (err) => {
// Attempt to add column for existing databases (fails silently if exists)
if (!err) {
db.run('ALTER TABLE profiles ADD COLUMN is_active INTEGER DEFAULT 0', () => {});
}
});
db.run(`CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
user_id TEXT,
name TEXT,
scene_data TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES profiles (id)
)`);
}
});
const run = (sql, params = []) => {
return new Promise((resolve, reject) => {
db.run(sql, params, function (err) {
if (err) reject(err);
else resolve({ id: this.lastID, changes: this.changes });
});
});
};
const get = (sql, params = []) => {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
const all = (sql, params = []) => {
return new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
};
module.exports = { db, run, get, all };
+18
View File
@@ -0,0 +1,18 @@
{
"name": "titan3d-backend",
"version": "1.0.0",
"description": "Local API for Titan3d",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"sqlite3": "^5.1.7",
"uuid": "^9.0.1"
}
}
+204
View File
@@ -0,0 +1,204 @@
const express = require('express');
const cors = require('cors');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { db, run, get, all } = require('./database');
const app = express();
app.use(cors());
app.use(express.json({ limit: '100mb' }));
const JWT_SECRET = 'viz-3d-local-secret-key-1234';
// Ensure uploads dir exists
const uploadsDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}
app.use('/uploads', express.static(uploadsDir));
// Multer storage
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadsDir);
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
// --- AUTH MIDDLEWARE ---
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).json({ error: 'No token provided' });
const token = authHeader.split(' ')[1];
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) return res.status(401).json({ error: 'Invalid token' });
req.user = decoded;
next();
});
};
// --- ROUTES ---
// 1. Auth Register
app.post('/api/auth/register', async (req, res) => {
const { email, password } = req.body;
if (!email || !password) return res.status(400).json({ error: 'Email and password required' });
try {
const existing = await get('SELECT * FROM profiles WHERE email = ?', [email]);
if (existing) return res.status(400).json({ error: 'User already exists' });
const hash = await bcrypt.hash(password, 10);
const id = uuidv4();
// First user becomes admin and is automatically active
const countRow = await get('SELECT COUNT(*) as count FROM profiles');
const role = countRow.count === 0 ? 'admin' : 'user';
const is_active = countRow.count === 0 ? 1 : 0;
await run(
'INSERT INTO profiles (id, email, password_hash, role, api_credits, storage_limit_mb, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)',
[id, email, hash, role, 10, 500, is_active]
);
if (is_active === 0) {
return res.json({ status: 'pending', message: 'Account created. Please contact an administrator to activate your account.' });
}
const token = jwt.sign({ id, email, role }, JWT_SECRET, { expiresIn: '7d' });
const profile = await get('SELECT id, email, role, api_credits, storage_limit_mb, is_active FROM profiles WHERE id = ?', [id]);
res.json({ status: 'active', token, profile });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 2. Auth Login
app.post('/api/auth/login', async (req, res) => {
const { email, password } = req.body;
try {
const user = await get('SELECT * FROM profiles WHERE email = ?', [email]);
if (!user) return res.status(400).json({ error: 'Invalid credentials' });
const valid = await bcrypt.compare(password, user.password_hash);
if (!valid) return res.status(400).json({ error: 'Invalid credentials' });
if (user.is_active === 0) {
return res.status(403).json({ error: 'Account pending admin approval. Please contact an administrator.' });
}
const token = jwt.sign({ id: user.id, email: user.email, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
const profile = { id: user.id, email: user.email, role: user.role, api_credits: user.api_credits, storage_limit_mb: user.storage_limit_mb, is_active: user.is_active };
res.json({ token, profile });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 3. Auth Me
app.get('/api/auth/me', authenticate, async (req, res) => {
try {
const profile = await get('SELECT id, email, role, api_credits, storage_limit_mb, is_active FROM profiles WHERE id = ?', [req.user.id]);
res.json({ profile });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 4. Get all projects for user
app.get('/api/projects', authenticate, async (req, res) => {
try {
const projects = await all('SELECT id, name FROM projects WHERE user_id = ?', [req.user.id]);
res.json({ projects });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 5. Get project by ID
app.get('/api/projects/:id', authenticate, async (req, res) => {
try {
const project = await get('SELECT * FROM projects WHERE id = ? AND user_id = ?', [req.params.id, req.user.id]);
if (!project) return res.status(404).json({ error: 'Project not found' });
if (project.scene_data) {
project.scene_data = JSON.parse(project.scene_data);
}
res.json({ project });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 6. Save (create or update) project
app.post('/api/projects', authenticate, async (req, res) => {
const { id, name, scene_data } = req.body;
const projectId = id || uuidv4();
try {
const existing = await get('SELECT * FROM projects WHERE id = ?', [projectId]);
if (existing) {
if (existing.user_id !== req.user.id) return res.status(403).json({ error: 'Forbidden' });
await run('UPDATE projects SET name = ?, scene_data = ? WHERE id = ?', [name, JSON.stringify(scene_data), projectId]);
} else {
await run('INSERT INTO projects (id, user_id, name, scene_data) VALUES (?, ?, ?, ?)', [projectId, req.user.id, name, JSON.stringify(scene_data)]);
}
res.json({ success: true, id: projectId });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 7. Get users (Admin only)
app.get('/api/users', authenticate, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
try {
const users = await all('SELECT id, email, role, api_credits, storage_limit_mb, is_active FROM profiles');
res.json({ users });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 8. Update user (Admin only)
app.put('/api/users/:id', authenticate, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
const { role, api_credits, storage_limit_mb, is_active } = req.body;
try {
await run('UPDATE profiles SET role = ?, api_credits = ?, storage_limit_mb = ?, is_active = ? WHERE id = ?', [role, api_credits, storage_limit_mb, is_active !== undefined ? is_active : 1, req.params.id]);
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 9. Deduct API credit
app.post('/api/users/deduct-credit', authenticate, async (req, res) => {
try {
const user = await get('SELECT api_credits FROM profiles WHERE id = ?', [req.user.id]);
if (user.api_credits <= 0) return res.status(400).json({ error: 'Not enough credits' });
await run('UPDATE profiles SET api_credits = api_credits - 1 WHERE id = ?', [req.user.id]);
const updated = await get('SELECT api_credits FROM profiles WHERE id = ?', [req.user.id]);
res.json({ api_credits: updated.api_credits });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 10. Upload file
app.post('/api/upload', authenticate, upload.single('file'), (req, res) => {
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
const url = `${req.protocol}://${req.get('host')}/uploads/${req.file.filename}`;
res.json({ url });
});
const PORT = process.env.PORT || 3005;
app.listen(PORT, () => {
console.log(`Backend server running on port ${PORT}`);
});