Implement SimpleNote Web API - full REST API with Express
- Express server with CORS, JSON middleware - Auth middleware (Bearer token) - Document CRUD with markdown storage - Library CRUD with nested support - Tag indexing and search - Error handler middleware - Config from env vars - Init script for data structure
This commit is contained in:
35
src/utils/errors.js
Normal file
35
src/utils/errors.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* SimpleNote Web - Custom Errors
|
||||
*/
|
||||
|
||||
export class AppError extends Error {
|
||||
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.code = code;
|
||||
this.name = 'AppError';
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends AppError {
|
||||
constructor(resource = 'Resource') {
|
||||
super(`${resource} not found`, 404, 'NOT_FOUND');
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends AppError {
|
||||
constructor(message = 'Unauthorized') {
|
||||
super(message, 401, 'UNAUTHORIZED');
|
||||
this.name = 'UnauthorizedError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ValidationError extends AppError {
|
||||
constructor(message) {
|
||||
super(message, 400, 'VALIDATION_ERROR');
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
export default { AppError, NotFoundError, UnauthorizedError, ValidationError };
|
||||
58
src/utils/fsHelper.js
Normal file
58
src/utils/fsHelper.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* SimpleNote Web - Filesystem Helper
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync, statSync } from 'fs';
|
||||
import { join, resolve, relative, sep } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
export function ensureDir(dirPath) {
|
||||
if (!existsSync(dirPath)) {
|
||||
mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function readJSON(filePath) {
|
||||
if (!existsSync(filePath)) return null;
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
export function writeJSON(filePath, data) {
|
||||
ensureDir(dirname(filePath));
|
||||
writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
export function deletePath(path) {
|
||||
if (existsSync(path)) {
|
||||
rmSync(path, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function pathExists(path) {
|
||||
return existsSync(path);
|
||||
}
|
||||
|
||||
export function listDir(dirPath) {
|
||||
if (!existsSync(dirPath)) return [];
|
||||
return readdirSync(dirPath, 'utf-8');
|
||||
}
|
||||
|
||||
export function isDirectory(path) {
|
||||
try {
|
||||
return statSync(path).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveSafe(base, target) {
|
||||
const resolved = resolve(base, target);
|
||||
if (!resolved.startsWith(resolve(base))) {
|
||||
throw new Error('Path traversal detected');
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export default { ensureDir, readJSON, writeJSON, deletePath, pathExists, listDir, isDirectory, resolveSafe };
|
||||
68
src/utils/markdown.js
Normal file
68
src/utils/markdown.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* SimpleNote Web - Markdown Utilities
|
||||
* Helpers for parsing frontmatter and serializing markdown documents
|
||||
*/
|
||||
|
||||
import matter from 'gray-matter';
|
||||
import { generateId } from './uuid.js';
|
||||
|
||||
const VALID_TYPES = ['requirement', 'note', 'spec', 'general'];
|
||||
const VALID_STATUSES = ['draft', 'approved', 'implemented'];
|
||||
const VALID_PRIORITIES = ['high', 'medium', 'low'];
|
||||
|
||||
export function parseMarkdown(content) {
|
||||
try {
|
||||
const { data, content: body } = matter(content);
|
||||
return {
|
||||
metadata: {
|
||||
id: data.id || null,
|
||||
title: data.title || 'Untitled',
|
||||
type: VALID_TYPES.includes(data.type) ? data.type : 'general',
|
||||
status: VALID_STATUSES.includes(data.status) ? data.status : 'draft',
|
||||
priority: VALID_PRIORITIES.includes(data.priority) ? data.priority : 'medium',
|
||||
tags: Array.isArray(data.tags) ? data.tags : [],
|
||||
createdBy: data.createdBy || null,
|
||||
createdAt: data.createdAt || new Date().toISOString(),
|
||||
},
|
||||
body: body.trim(),
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
metadata: {
|
||||
id: null,
|
||||
title: 'Untitled',
|
||||
type: 'general',
|
||||
status: 'draft',
|
||||
priority: 'medium',
|
||||
tags: [],
|
||||
createdBy: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
body: content,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeMarkdown(metadata, body = '') {
|
||||
const frontmatter = [
|
||||
'---',
|
||||
`id: ${metadata.id || generateId()}`,
|
||||
`title: ${metadata.title || 'Untitled'}`,
|
||||
`type: ${metadata.type || 'general'}`,
|
||||
`priority: ${metadata.priority || 'medium'}`,
|
||||
`status: ${metadata.status || 'draft'}`,
|
||||
`tags: [${(metadata.tags || []).join(', ')}]`,
|
||||
metadata.createdBy ? `createdBy: ${metadata.createdBy}` : null,
|
||||
`createdAt: ${metadata.createdAt || new Date().toISOString().split('T')[0]}`,
|
||||
'---',
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
return `${frontmatter}\n\n${body}`;
|
||||
}
|
||||
|
||||
export function buildDefaultContent(title, type = 'general') {
|
||||
const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
return `# ${title}\n\n## Descripción\nDescripción del ${typeLabel}.\n\n## Criterios de Aceptación\n- [ ] Criterio 1\n- [ ] Criterio 2\n`;
|
||||
}
|
||||
|
||||
export default { parseMarkdown, serializeMarkdown, buildDefaultContent };
|
||||
11
src/utils/uuid.js
Normal file
11
src/utils/uuid.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* SimpleNote Web - UUID Helper
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function generateId() {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
export default { generateId };
|
||||
Reference in New Issue
Block a user