feat: menu Notas -- apuntes rapidos personales
Nuevo menu "Notas" en el sidebar: crear, editar y eliminar apuntes rapidos, con fecha y hora de creacion (y de ultima edicion si aplica). Siempre privadas de cada usuario -- una nota de otro usuario responde 404, no 403, para no revelar que existe. Sin restriccion de rol. Nuevo notes (tabla) + GET/POST/PUT/DELETE /api/notes. Probado de punta a punta en dev: crear, editar, borrar y el formato de fecha/hora funcionan correctamente. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
-- Apuntes rapidos personales (menu "Notas"): siempre privados de cada usuario, sin permisos
|
||||
-- especiales de rol -- cualquiera autenticado ve/edita/borra solo los suyos.
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_notes_user (user_id),
|
||||
CONSTRAINT fk_notes_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "project-linkedcorp-backend",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
|
||||
@@ -22,6 +22,7 @@ import recurringTasksRoutes from './modules/recurring-tasks/routes.js';
|
||||
import searchRoutes from './modules/search/routes.js';
|
||||
import updaterRoutes from './modules/updater/routes.js';
|
||||
import errorLogsRoutes from './modules/errorLogs/routes.js';
|
||||
import notesRoutes from './modules/notes/routes.js';
|
||||
|
||||
export const app = express();
|
||||
|
||||
@@ -48,6 +49,7 @@ app.use('/api/recurring-tasks', recurringTasksRoutes);
|
||||
app.use('/api/search', searchRoutes);
|
||||
app.use('/api/updater', updaterRoutes);
|
||||
app.use('/api/error-logs', errorLogsRoutes);
|
||||
app.use('/api/notes', notesRoutes);
|
||||
// Mas generico (mount en /api sin sub-path propio) — SIEMPRE al final de los /api/*, para no
|
||||
// interceptar con su requireAuth interno una ruta que en realidad pertenece a un router mas
|
||||
// especifico registrado despues (ver ESTADO.md, deuda tecnica).
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ok, asyncHandler } from '../../utils/http.js';
|
||||
import * as service from './service.js';
|
||||
|
||||
export const getNotes = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.listNotes(req.currentUser));
|
||||
});
|
||||
|
||||
export const postNote = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.createNote(req.body.content, req.currentUser), 201);
|
||||
});
|
||||
|
||||
export const putNote = asyncHandler(async (req, res) => {
|
||||
ok(res, await service.updateNote(Number(req.params.id), req.body.content, req.currentUser));
|
||||
});
|
||||
|
||||
export const deleteNote = asyncHandler(async (req, res) => {
|
||||
await service.removeNote(Number(req.params.id), req.currentUser);
|
||||
ok(res, { deleted: true });
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { pool } from '../../config/db.js';
|
||||
|
||||
export async function findByUser(userId) {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT id, user_id, content, created_at, updated_at FROM notes WHERE user_id = ? ORDER BY updated_at DESC',
|
||||
[userId]
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function findById(id) {
|
||||
const [rows] = await pool.query('SELECT id, user_id, content, created_at, updated_at FROM notes WHERE id = ?', [id]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function create(userId, content) {
|
||||
const [result] = await pool.query('INSERT INTO notes (user_id, content) VALUES (?, ?)', [userId, content]);
|
||||
return findById(result.insertId);
|
||||
}
|
||||
|
||||
export async function update(id, content) {
|
||||
await pool.query('UPDATE notes SET content = ? WHERE id = ?', [content, id]);
|
||||
return findById(id);
|
||||
}
|
||||
|
||||
export async function remove(id) {
|
||||
await pool.query('DELETE FROM notes WHERE id = ?', [id]);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Router } from 'express';
|
||||
import { requireAuth } from '../../middleware/auth.js';
|
||||
import { getNotes, postNote, putNote, deleteNote } from './controller.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Sin restriccion de rol: cualquier usuario autenticado tiene sus propias notas.
|
||||
router.use(requireAuth);
|
||||
router.get('/', getNotes);
|
||||
router.post('/', postNote);
|
||||
router.put('/:id', putNote);
|
||||
router.delete('/:id', deleteNote);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as repo from './repository.js';
|
||||
import { HttpError } from '../../utils/http.js';
|
||||
|
||||
// Siempre privadas: cada quien ve y toca solo sus propias notas, sin importar el rol. Una nota
|
||||
// de otro usuario responde 404 (no 403) para no revelar que existe.
|
||||
async function getOwnNoteOr404(id, currentUser) {
|
||||
const note = await repo.findById(id);
|
||||
if (!note || note.user_id !== currentUser.id) throw new HttpError(404, 'Nota no encontrada');
|
||||
return note;
|
||||
}
|
||||
|
||||
export const listNotes = (currentUser) => repo.findByUser(currentUser.id);
|
||||
|
||||
export async function createNote(content, currentUser) {
|
||||
const trimmed = (content || '').trim();
|
||||
if (!trimmed) throw new HttpError(400, 'El contenido es obligatorio');
|
||||
return repo.create(currentUser.id, trimmed);
|
||||
}
|
||||
|
||||
export async function updateNote(id, content, currentUser) {
|
||||
await getOwnNoteOr404(id, currentUser);
|
||||
const trimmed = (content || '').trim();
|
||||
if (!trimmed) throw new HttpError(400, 'El contenido es obligatorio');
|
||||
return repo.update(id, trimmed);
|
||||
}
|
||||
|
||||
export async function removeNote(id, currentUser) {
|
||||
await getOwnNoteOr404(id, currentUser);
|
||||
await repo.remove(id);
|
||||
}
|
||||
Reference in New Issue
Block a user