Clean up: Remove services files
This commit is contained in:
parent
603be6cbd8
commit
4375aedb34
@ -1,259 +0,0 @@
|
|||||||
import {
|
|
||||||
Task,
|
|
||||||
TaskDraft,
|
|
||||||
TASK_PRIORITIES,
|
|
||||||
TASK_STATUSES,
|
|
||||||
TITLE_MAX_LENGTH,
|
|
||||||
DESCRIPTION_MAX_LENGTH,
|
|
||||||
User,
|
|
||||||
} from '../types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In-memory stand-in for the REST API described in
|
|
||||||
* architect/03-feasibility/frontend-handoff.md. Method names and payload shapes
|
|
||||||
* match the documented endpoints so the Backend phase can swap the bodies for
|
|
||||||
* fetch/axios calls without touching any component.
|
|
||||||
*
|
|
||||||
* GET /api/auth/me -> getCurrentUser()
|
|
||||||
* GET /api/users -> getUsers()
|
|
||||||
* GET /api/tasks -> getTasks()
|
|
||||||
* POST /api/tasks -> createTask(draft)
|
|
||||||
* PUT /api/tasks/:id -> updateTask(id, changes)
|
|
||||||
* DELETE /api/tasks/:id -> deleteTask(id)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Error type carrying a message that is safe to show a user. */
|
|
||||||
export class ApiError extends Error {
|
|
||||||
status: number;
|
|
||||||
|
|
||||||
constructor(message: string, status = 400) {
|
|
||||||
super(message);
|
|
||||||
this.name = 'ApiError';
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const users: User[] = [
|
|
||||||
{ id: 1, username: 'sarah.chen', full_name: 'Sarah Chen' },
|
|
||||||
{ id: 2, username: 'alex.rivera', full_name: 'Alex Rivera' },
|
|
||||||
{ id: 3, username: 'david.kim', full_name: 'David Kim' },
|
|
||||||
{ id: 4, username: 'maria.garcia', full_name: 'Maria Garcia' },
|
|
||||||
{ id: 5, username: 'ben.carter', full_name: 'Ben Carter' },
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Signed-in user. Real implementation reads this from GET /api/auth/me. */
|
|
||||||
const currentUser = users[0];
|
|
||||||
|
|
||||||
const isoDate = (dayOffset: number): string => {
|
|
||||||
const date = new Date();
|
|
||||||
date.setDate(date.getDate() + dayOffset);
|
|
||||||
return date.toISOString().slice(0, 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
const timestamp = (dayOffset: number): string => {
|
|
||||||
const date = new Date();
|
|
||||||
date.setDate(date.getDate() + dayOffset);
|
|
||||||
return date.toISOString();
|
|
||||||
};
|
|
||||||
|
|
||||||
let tasks: Task[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: 'Website Redesign - Homepage Layout',
|
|
||||||
description: 'Finalize wireframes and mobile view',
|
|
||||||
assigned_to: users[1],
|
|
||||||
status: 'in_progress',
|
|
||||||
priority: 'high',
|
|
||||||
due_date: isoDate(5),
|
|
||||||
created_by: currentUser,
|
|
||||||
created_at: timestamp(-9),
|
|
||||||
updated_at: timestamp(-1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: 'Q4 Marketing Strategy',
|
|
||||||
description: 'Outline key campaigns and budget',
|
|
||||||
assigned_to: users[0],
|
|
||||||
status: 'open',
|
|
||||||
priority: 'medium',
|
|
||||||
due_date: isoDate(10),
|
|
||||||
created_by: users[2],
|
|
||||||
created_at: timestamp(-7),
|
|
||||||
updated_at: timestamp(-7),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: 'API Integration Debugging',
|
|
||||||
description: 'Fixing errors in payment gateway',
|
|
||||||
assigned_to: users[2],
|
|
||||||
status: 'blocked',
|
|
||||||
priority: 'high',
|
|
||||||
due_date: isoDate(8),
|
|
||||||
created_by: currentUser,
|
|
||||||
created_at: timestamp(-6),
|
|
||||||
updated_at: timestamp(-2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: 'Partner Onboarding Playbook',
|
|
||||||
description: 'Document the hand-off steps for new partners',
|
|
||||||
assigned_to: users[2],
|
|
||||||
status: 'in_progress',
|
|
||||||
priority: 'high',
|
|
||||||
due_date: isoDate(-2),
|
|
||||||
created_by: users[3],
|
|
||||||
created_at: timestamp(-14),
|
|
||||||
updated_at: timestamp(-3),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: 'API Integration Review',
|
|
||||||
description: 'Second pass over the payment gateway contract',
|
|
||||||
assigned_to: users[0],
|
|
||||||
status: 'open',
|
|
||||||
priority: 'low',
|
|
||||||
due_date: isoDate(6),
|
|
||||||
created_by: users[1],
|
|
||||||
created_at: timestamp(-4),
|
|
||||||
updated_at: timestamp(-4),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: 'Mobile Nav Accessibility Audit',
|
|
||||||
description: 'Keyboard and screen reader pass on the mobile navigation',
|
|
||||||
assigned_to: users[3],
|
|
||||||
status: 'blocked',
|
|
||||||
priority: 'medium',
|
|
||||||
due_date: isoDate(12),
|
|
||||||
created_by: currentUser,
|
|
||||||
created_at: timestamp(-3),
|
|
||||||
updated_at: timestamp(-1),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const delay = (ms: number): Promise<void> =>
|
|
||||||
new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
const clone = (task: Task): Task => ({ ...task });
|
|
||||||
|
|
||||||
const nextId = (): number =>
|
|
||||||
tasks.reduce((highest, task) => Math.max(highest, task.id), 0) + 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates a draft before it reaches the store. The UI validates too, but this
|
|
||||||
* is the trust boundary the real API will guard, so the rules live here as well.
|
|
||||||
*/
|
|
||||||
const validateDraft = (draft: Partial<TaskDraft>, requireAll: boolean): void => {
|
|
||||||
if (requireAll || draft.title !== undefined) {
|
|
||||||
const title = (draft.title ?? '').trim();
|
|
||||||
if (!title) {
|
|
||||||
throw new ApiError('A task title is required.');
|
|
||||||
}
|
|
||||||
if (title.length > TITLE_MAX_LENGTH) {
|
|
||||||
throw new ApiError(`Keep the title under ${TITLE_MAX_LENGTH} characters.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draft.description !== undefined && draft.description.length > DESCRIPTION_MAX_LENGTH) {
|
|
||||||
throw new ApiError(`Keep the description under ${DESCRIPTION_MAX_LENGTH} characters.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requireAll || draft.assignee_id !== undefined) {
|
|
||||||
const assignee = users.find((user) => user.id === draft.assignee_id);
|
|
||||||
if (!assignee) {
|
|
||||||
throw new ApiError('Assign this task to a current team member.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draft.status !== undefined && !TASK_STATUSES.includes(draft.status)) {
|
|
||||||
throw new ApiError('Choose a valid status.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draft.priority !== undefined && !TASK_PRIORITIES.includes(draft.priority)) {
|
|
||||||
throw new ApiError('Choose a valid priority.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draft.due_date) {
|
|
||||||
if (Number.isNaN(Date.parse(draft.due_date))) {
|
|
||||||
throw new ApiError('Enter a due date in YYYY-MM-DD format.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const api = {
|
|
||||||
async getCurrentUser(): Promise<User> {
|
|
||||||
await delay(150);
|
|
||||||
return { ...currentUser };
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUsers(): Promise<User[]> {
|
|
||||||
await delay(200);
|
|
||||||
return users.map((user) => ({ ...user }));
|
|
||||||
},
|
|
||||||
|
|
||||||
async getTasks(): Promise<Task[]> {
|
|
||||||
await delay(400);
|
|
||||||
return tasks.map(clone);
|
|
||||||
},
|
|
||||||
|
|
||||||
async createTask(draft: TaskDraft): Promise<Task> {
|
|
||||||
await delay(350);
|
|
||||||
validateDraft(draft, true);
|
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
|
||||||
const task: Task = {
|
|
||||||
id: nextId(),
|
|
||||||
title: draft.title.trim(),
|
|
||||||
description: draft.description.trim(),
|
|
||||||
assigned_to: users.find((user) => user.id === draft.assignee_id),
|
|
||||||
status: draft.status,
|
|
||||||
priority: draft.priority,
|
|
||||||
due_date: draft.due_date || undefined,
|
|
||||||
created_by: { ...currentUser },
|
|
||||||
created_at: now,
|
|
||||||
updated_at: now,
|
|
||||||
};
|
|
||||||
|
|
||||||
tasks = [task, ...tasks];
|
|
||||||
return clone(task);
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateTask(id: number, changes: Partial<TaskDraft>): Promise<Task> {
|
|
||||||
await delay(300);
|
|
||||||
const existing = tasks.find((task) => task.id === id);
|
|
||||||
if (!existing) {
|
|
||||||
throw new ApiError('That task no longer exists. Refresh to see the latest list.', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
validateDraft(changes, false);
|
|
||||||
|
|
||||||
const updated: Task = {
|
|
||||||
...existing,
|
|
||||||
title: changes.title !== undefined ? changes.title.trim() : existing.title,
|
|
||||||
description:
|
|
||||||
changes.description !== undefined ? changes.description.trim() : existing.description,
|
|
||||||
assigned_to:
|
|
||||||
changes.assignee_id !== undefined
|
|
||||||
? users.find((user) => user.id === changes.assignee_id)
|
|
||||||
: existing.assigned_to,
|
|
||||||
status: changes.status ?? existing.status,
|
|
||||||
priority: changes.priority ?? existing.priority,
|
|
||||||
due_date: changes.due_date !== undefined ? changes.due_date || undefined : existing.due_date,
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
tasks = tasks.map((task) => (task.id === id ? updated : task));
|
|
||||||
return clone(updated);
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteTask(id: number): Promise<void> {
|
|
||||||
await delay(250);
|
|
||||||
const exists = tasks.some((task) => task.id === id);
|
|
||||||
if (!exists) {
|
|
||||||
throw new ApiError('That task no longer exists. Refresh to see the latest list.', 404);
|
|
||||||
}
|
|
||||||
tasks = tasks.filter((task) => task.id !== id);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Api = typeof api;
|
|
||||||
Loading…
Reference in New Issue
Block a user