close to final version added the subtaskand comment working section

This commit is contained in:
tusuii
2026-02-16 19:50:23 +05:30
parent 6aec1445e9
commit 1788e364f1
28 changed files with 5867 additions and 133 deletions

16
server/app_spin.js Normal file
View File

@@ -0,0 +1,16 @@
async function handleRequest(req, res) {
console.log("Handle called");
return {
status: 200,
headers: { 'content-type': 'text/plain' },
body: 'Hello from Spin Wasm (Corrected Export)!'
};
}
export const incomingHandler = {
handle: handleRequest
};
// Keep default just in case, but incomingHandler is key
export default handleRequest;

137
server/app_spin.js.bak Normal file
View File

@@ -0,0 +1,137 @@
import { Hono } from 'hono';
import { handleRequest } from '@fermyon/spin-sdk';
const app = new Hono();
// Middleware to mock Express request/response specific methods if needed
// Or we just rewrite routes to use Hono context.
// Since rewriting all routes is heavy, let's try to mount simple wrappers
// or just import the router logic if we refactor routes.
// Given the implementation plan said "Re-implement routing logic",
// and the routes are currently Express routers, we probably need to wrap them
// or quickly rewrite them to Hono.
// Strategy: Import the routes and mount them.
// BUT Express routers won't work in Hono.
// We must rewrite the route definitions in this file or transformed files.
// For "quick deployment", I will inline the mounting of existing logic where possible,
// using the db_spin adapter.
import pool from './db_spin.js';
import bcrypt from 'bcryptjs';
// import { randomUUID } from 'crypto'; // Use global crypto
const randomUUID = () => crypto.randomUUID();
// --- AUTH ROUTES ---
app.post('/api/auth/login', async (c) => {
try {
const { email, password } = await c.req.json();
if (!email || !password) return c.json({ error: 'Email and password required' }, 400);
const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email]);
if (rows.length === 0) return c.json({ error: 'Invalid email or password' }, 401);
const user = rows[0];
const valid = await bcrypt.compare(password, user.password_hash);
if (!valid) return c.json({ error: 'Invalid email or password' }, 401);
return c.json({
id: user.id, name: user.name, role: user.role, email: user.email,
color: user.color, avatar: user.avatar, dept: user.dept,
});
} catch (e) {
console.error(e);
return c.json({ error: 'Internal server error' }, 500);
}
});
app.post('/api/auth/register', async (c) => {
try {
const { name, email, password, role, dept } = await c.req.json();
if (!name || !email || !password) return c.json({ error: 'Required fields missing' }, 400);
const [existing] = await pool.query('SELECT id FROM users WHERE email = ?', [email]);
if (existing.length > 0) return c.json({ error: 'Email already registered' }, 409);
const id = randomUUID();
const password_hash = await bcrypt.hash(password, 10);
// ... (simplified avatar logic)
const avatar = name.substring(0, 2).toUpperCase();
const color = '#818cf8';
await pool.query(
'INSERT INTO users (id, name, role, email, password_hash, color, avatar, dept) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, name, role || 'employee', email, password_hash, color, avatar, dept || '']
);
return c.json({ id, name, role: role || 'employee', email, color, avatar, dept: dept || '' }, 201);
} catch (e) {
console.error(e);
return c.json({ error: 'Internal server error' }, 500);
}
});
// --- TASKS ROUTES (Simplified for Wasm demo) ---
async function getFullTask(taskId) {
const [taskRows] = await pool.query('SELECT * FROM tasks WHERE id = ?', [taskId]);
if (taskRows.length === 0) return null;
const task = taskRows[0];
// For brevity, not fetching sub-resources in this quick conversion,
// but in full prod we would.
// ... complete implementation would replicate existing logic ...
return task;
}
app.get('/api/tasks', async (c) => {
try {
const [rows] = await pool.query('SELECT * FROM tasks ORDER BY created_at DESC');
// Simplify for now: Just return tasks
return c.json(rows);
} catch (e) {
console.error(e);
return c.json({ error: 'Internal server error' }, 500);
}
});
app.post('/api/tasks', async (c) => {
try {
const { title, description, status, priority, assignee, reporter, dueDate } = await c.req.json();
const id = randomUUID();
await pool.query(
'INSERT INTO tasks (id, title, description, status, priority, assignee_id, reporter_id, due_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[id, title, description || '', status || 'todo', priority || 'medium', assignee || null, reporter || null, dueDate || null]
);
return c.json({ id, title, status }, 201);
} catch (e) {
console.error(e);
return c.json({ error: 'Internal server error' }, 500);
}
});
// Health check
app.get('/api/health', (c) => c.json({ status: 'ok', engine: 'spin-wasm' }));
// Export the Spin handler
export const spinHandler = async (req, res) => {
// Spin generic handler interacting with Hono?
// Hono has a fetch method: app.fetch(request, env, ctx)
// Spin request is slightly different, but let's see if we can adapt.
// Actually, Spin JS SDK v2 exports `handleRequest` which takes (request, response).
// Hono might need an adapter.
// Simple adapter for Hono .fetch to Spin
// Construct standard Request object from Spin calls if needed,
// but simpler to use Hono's handle() if passing standard web standards.
// Assuming standard web signature is passed by recent Spin SDKs or we use 'node-adapter' if built via bundling.
// But since we are likely using a bundler, strict Spin API is:
// export async function handleRequest(request: Request): Promise<Response>
return app.fetch(req);
};
// If using valid Spin JS plugin that looks for `handleRequest` as default export or named export
// We will export it as `handleRequest` (default)
export default async function (req) {
return await app.fetch(req);
}

17
server/build.mjs Normal file
View File

@@ -0,0 +1,17 @@
import { build } from 'esbuild';
import { SpinEsbuildPlugin } from "@spinframework/build-tools/plugins/esbuild/index.js";
const spinPlugin = await SpinEsbuildPlugin();
await build({
entryPoints: ['./app_spin.js'],
outfile: './dist/spin.js',
bundle: true,
format: 'esm',
platform: 'node',
sourcemap: false,
minify: false,
plugins: [spinPlugin],
target: 'es2020',
external: ['fermyon:*', 'spin:*'],
});

94
server/db_spin.js Normal file
View File

@@ -0,0 +1,94 @@
import { Mysql } from '@fermyon/spin-sdk';
const getEnv = (key, def) => {
try {
return (typeof process !== 'undefined' && process.env && process.env[key]) || def;
} catch {
return def;
}
};
const DB_URL = `mysql://${getEnv('DB_USER', 'root')}:${getEnv('DB_PASSWORD', 'scrumpass')}@${getEnv('DB_HOST', 'localhost')}:${getEnv('DB_PORT', '3306')}/${getEnv('DB_NAME', 'scrum_manager')}`;
function rowToObject(row, columns) {
const obj = {};
columns.forEach((col, index) => {
obj[col.name] = row[index];
});
return obj;
}
class SpinConnection {
constructor(conn) {
this.conn = conn;
}
async query(sql, params = []) {
console.log('SpinDB Query:', sql, params);
try {
const result = this.conn.query(sql, params);
const rows = result.rows.map(r => rowToObject(r, result.columns));
const fields = result.columns.map(c => ({ name: c.name }));
if (sql.trim().toUpperCase().startsWith('INSERT') || sql.trim().toUpperCase().startsWith('UPDATE') || sql.trim().toUpperCase().startsWith('DELETE')) {
return [{ affectedRows: result.rowsAffected || 0, insertId: result.lastInsertId || 0 }, fields];
}
return [rows, fields];
} catch (e) {
console.error('SpinDB Error:', e);
throw e;
}
}
async beginTransaction() {
try {
this.conn.query('START TRANSACTION', []);
} catch (e) {
console.warn('Transaction start failed:', e.message);
}
}
async commit() {
try { this.conn.query('COMMIT', []); } catch (e) { }
}
async rollback() {
try { this.conn.query('ROLLBACK', []); } catch (e) { }
}
release() { }
}
export const initDB = async () => {
console.log('Spin DB adapter ready.');
};
// Lazy initialization to avoid Wizer issues
let poolInstance = null;
function getPool() {
if (!poolInstance) {
poolInstance = {
async getConnection() {
const conn = Mysql.open(DB_URL);
return new SpinConnection(conn);
},
async query(sql, params) {
const conn = await this.getConnection();
const result = await conn.query(sql, params);
return result;
},
escape: (val) => `'${val}'`,
end: () => { }
};
}
return poolInstance;
}
export default {
query: (sql, params) => getPool().query(sql, params),
getConnection: () => getPool().getConnection(),
end: () => getPool().end(),
initDB
};

10
server/knitwit.json Normal file
View File

@@ -0,0 +1,10 @@
{
"packages": {
"@fermyon/spin-sdk": {
"witPath": "../../bin/wit",
"world": "spin-imports"
}
},
"project": {},
"version": 1
}

3250
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,15 +5,21 @@
"type": "module",
"scripts": {
"start": "node index.js",
"test": "vitest"
"test": "vitest",
"build:spin": "node build.mjs && node node_modules/@fermyon/spin-sdk/bin/j2w.mjs -i dist/spin.js -o dist/main.wasm --trigger-type spin3-http"
},
"dependencies": {
"@fermyon/spin-sdk": "^2.2.0",
"@spinframework/wasi-http-proxy": "^1.0.0",
"bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"express": "^5.1.0",
"hono": "^4.6.14",
"mysql2": "^3.14.1"
},
"devDependencies": {
"@spinframework/build-tools": "^1.0.7",
"esbuild": "^0.24.2",
"supertest": "^7.2.2",
"vitest": "^4.0.18"
}

23
server/polyfill.js Normal file
View File

@@ -0,0 +1,23 @@
// Polyfill for crypto in Wizer environment
if (!globalThis.crypto) {
globalThis.crypto = {
getRandomValues: (buffer) => {
// Check if buffer is valid
if (!buffer || typeof buffer.length !== 'number') {
throw new Error("crypto.getRandomValues: invalid buffer");
}
// Fill with pseudo-random numbers
for (let i = 0; i < buffer.length; i++) {
buffer[i] = Math.floor(Math.random() * 256);
}
return buffer;
},
randomUUID: () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
};
console.log("Polyfilled globalThis.crypto for Wizer/Spin");
}