godcrm/backend/routes/v3/fitness/workoutController.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

201 lines
5.7 KiB
JavaScript

/**
* fitness/workoutController.js — Workouts CRUD
*/
import express from 'express';
import { dbAll, dbGet, dbRun, sqlNow } from '../../../database/connection.js';
import { apiLogger } from '../../../utils/logger.js';
import { success, created, error, badRequest, notFound } from '../../../utils/response.js';
const router = express.Router();
// =============================================================================
// WORKOUTS CRUD
// =============================================================================
/**
* POST /api/v3/fitness/workouts
* Create a new workout session
* @swagger
* /api/v3/fitness/workouts:
* post:
* summary: Create a new workout session
* tags: [Fitness]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Workout'
* responses:
* 201:
* description: Workout created
*/
router.post('/workouts', async (req, res) => {
try {
const { space_id, title, description, started_at, ended_at, notes, source = 'manual' } = req.body;
// Validation
if (!space_id) {
return badRequest(res, 'space_id is required', 'VALIDATION_ERROR');
}
if (!started_at) {
return badRequest(res, 'started_at is required', 'VALIDATION_ERROR');
}
const result = await dbRun(`
INSERT INTO fitness_workouts (space_id, title, description, started_at, ended_at, notes, source, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ${sqlNow()}, ${sqlNow()})
`, [space_id, title || null, description || null, started_at, ended_at || null, notes || null, source]);
const workout = await dbGet('SELECT * FROM fitness_workouts WHERE id = ?', [result.lastInsertRowid]);
apiLogger.info({ workoutId: workout.id, spaceId: space_id }, 'Workout created');
return created(res, workout);
} catch (err) {
apiLogger.error({ err }, 'POST /fitness/workouts error');
return error(res, 'CREATE_FAILED', err.message, 500);
}
});
/**
* GET /api/v3/fitness/workouts
* List workouts for a space
* @swagger
* /api/v3/fitness/workouts:
* get:
* summary: List workouts for a space
* tags: [Fitness]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: space_id
* schema:
* type: integer
* - in: query
* name: limit
* schema:
* type: integer
* responses:
* 200:
* description: List of workouts
*/
router.get('/workouts', async (req, res) => {
try {
const { space_id, limit = 50, offset = 0 } = req.query;
let sql = 'SELECT * FROM fitness_workouts';
const params = [];
if (space_id) {
sql += ' WHERE space_id = ?';
params.push(space_id);
}
sql += ' ORDER BY started_at DESC LIMIT ? OFFSET ?';
params.push(parseInt(limit), parseInt(offset));
const workouts = await dbAll(sql, params);
return success(res, workouts || []);
} catch (err) {
apiLogger.error({ err }, 'GET /fitness/workouts error');
return error(res, 'FETCH_FAILED', err.message, 500);
}
});
/**
* GET /api/v3/fitness/workouts/:id
* Get single workout with its sets
*/
router.get('/workouts/:id', async (req, res) => {
try {
const { id } = req.params;
const workout = await dbGet('SELECT * FROM fitness_workouts WHERE id = ?', [id]);
if (!workout) {
return notFound(res, 'Workout not found');
}
// Get sets for this workout
const sets = await dbAll(
'SELECT * FROM fitness_sets WHERE workout_id = ? ORDER BY set_index',
[id]
);
return success(res, { ...workout, sets: sets || [] });
} catch (err) {
apiLogger.error({ err }, 'GET /fitness/workouts/:id error');
return error(res, 'FETCH_FAILED', err.message, 500);
}
});
/**
* PUT /api/v3/fitness/workouts/:id
* Update a workout
*/
router.put('/workouts/:id', async (req, res) => {
try {
const { id } = req.params;
const { title, description, started_at, ended_at, notes } = req.body;
// Check workout exists
const existing = await dbGet('SELECT * FROM fitness_workouts WHERE id = ?', [id]);
if (!existing) {
return notFound(res, 'Workout not found');
}
await dbRun(`
UPDATE fitness_workouts
SET title = COALESCE(?, title),
description = COALESCE(?, description),
started_at = COALESCE(?, started_at),
ended_at = COALESCE(?, ended_at),
notes = COALESCE(?, notes),
updated_at = ${sqlNow()}
WHERE id = ?
`, [title, description, started_at, ended_at, notes, id]);
const updated = await dbGet('SELECT * FROM fitness_workouts WHERE id = ?', [id]);
apiLogger.info({ workoutId: id }, 'Workout updated');
return success(res, updated);
} catch (err) {
apiLogger.error({ err }, 'PUT /fitness/workouts/:id error');
return error(res, 'UPDATE_FAILED', err.message, 500);
}
});
/**
* DELETE /api/v3/fitness/workouts/:id
* Delete a workout (cascades to sets)
*/
router.delete('/workouts/:id', async (req, res) => {
try {
const { id } = req.params;
// Check workout exists
const existing = await dbGet('SELECT * FROM fitness_workouts WHERE id = ?', [id]);
if (!existing) {
return notFound(res, 'Workout not found');
}
await dbRun('DELETE FROM fitness_workouts WHERE id = ?', [id]);
apiLogger.info({ workoutId: id }, 'Workout deleted');
return success(res, { deleted: true, id: parseInt(id) });
} catch (err) {
apiLogger.error({ err }, 'DELETE /fitness/workouts/:id error');
return error(res, 'DELETE_FAILED', err.message, 500);
}
});
export default router;