init commit,
This commit is contained in:
42
03_source/cms_backend/src/app/api/auth/me/route.ts
Normal file
42
03_source/cms_backend/src/app/api/auth/me/route.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
import { verify } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _users, JWT_SECRET } from 'src/_mock/_auth';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const headersList = headers();
|
||||
const authorization = headersList.get('authorization');
|
||||
|
||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
||||
return response({ message: 'Authorization token missing or invalid' }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const accessToken = `${authorization}`.split(' ')[1];
|
||||
const data = await verify(accessToken, JWT_SECRET);
|
||||
|
||||
const currentUser = _users.find((user) => user.id === data.userId);
|
||||
|
||||
if (!currentUser) {
|
||||
return response({ message: 'Invalid authorization token' }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return response({ user: currentUser }, 200);
|
||||
} catch (error) {
|
||||
return handleError('[Auth] - Me', error);
|
||||
}
|
||||
}
|
45
03_source/cms_backend/src/app/api/auth/sign-in/route.ts
Normal file
45
03_source/cms_backend/src/app/api/auth/sign-in/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { sign } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _users, JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { email, password } = await req.json();
|
||||
|
||||
const currentUser = _users.find((user) => user.email === email);
|
||||
|
||||
if (!currentUser) {
|
||||
return response(
|
||||
{ message: 'There is no user corresponding to the email address.' },
|
||||
STATUS.UNAUTHORIZED
|
||||
);
|
||||
}
|
||||
|
||||
if (currentUser?.password !== password) {
|
||||
return response({ message: 'Wrong password' }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const accessToken = await sign({ userId: currentUser?.id }, JWT_SECRET, {
|
||||
expiresIn: JWT_EXPIRES_IN,
|
||||
});
|
||||
|
||||
return response({ user: currentUser, accessToken }, 200);
|
||||
} catch (error) {
|
||||
return handleError('Auth - Sign in', error);
|
||||
}
|
||||
}
|
61
03_source/cms_backend/src/app/api/auth/sign-up/route.ts
Normal file
61
03_source/cms_backend/src/app/api/auth/sign-up/route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { sign } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _users, JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { email, password, firstName, lastName } = await req.json();
|
||||
|
||||
const userExists = _users.find((user) => user.email === email);
|
||||
|
||||
if (userExists) {
|
||||
return response(
|
||||
{ message: 'There already exists an account with the given email address.' },
|
||||
STATUS.CONFLICT
|
||||
);
|
||||
}
|
||||
|
||||
const newUser = {
|
||||
id: _users[0].id,
|
||||
displayName: `${firstName} ${lastName}`,
|
||||
email,
|
||||
password,
|
||||
photoURL: '',
|
||||
phoneNumber: '',
|
||||
country: '',
|
||||
address: '',
|
||||
state: '',
|
||||
city: '',
|
||||
zipCode: '',
|
||||
about: '',
|
||||
role: 'user',
|
||||
isPublic: true,
|
||||
};
|
||||
|
||||
const accessToken = await sign({ userId: newUser.id }, JWT_SECRET, {
|
||||
expiresIn: JWT_EXPIRES_IN,
|
||||
});
|
||||
|
||||
// Push new user to database
|
||||
_users.push(newUser);
|
||||
|
||||
return response({ user: newUser, accessToken }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Auth - Sign up', error);
|
||||
}
|
||||
}
|
109
03_source/cms_backend/src/app/api/calendar/route.ts
Normal file
109
03_source/cms_backend/src/app/api/calendar/route.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _events } from 'src/_mock/_event';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
type EventType = ReturnType<typeof _events>[number];
|
||||
let eventsData: Map<string, EventType> = new Map();
|
||||
|
||||
function loggerData(action?: string, value?: unknown) {
|
||||
logger('[Event] total-events', eventsData.size);
|
||||
if (value || action) {
|
||||
logger(`[Event] ${action}`, value);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeEvents() {
|
||||
if (eventsData.size === 0) {
|
||||
const events = _events();
|
||||
eventsData = new Map(events.map((event) => [event.id, event]));
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* GET - All events
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
initializeEvents();
|
||||
|
||||
loggerData();
|
||||
|
||||
return response({ events: Array.from(eventsData.values()) }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Get all', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* POST - Create event
|
||||
*************************************** */
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { eventData } = await req.json();
|
||||
|
||||
eventsData.set(eventData.id, eventData);
|
||||
|
||||
loggerData('created', eventData);
|
||||
|
||||
return response({ event: eventData }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Create', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* PUT - Update event
|
||||
*************************************** */
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const { eventData } = await req.json();
|
||||
|
||||
if (!eventsData.has(eventData.id)) {
|
||||
return response({ message: 'Event not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
const event = eventsData.get(eventData.id);
|
||||
|
||||
// Merge the existing event with the updated data
|
||||
const updatedEvent = {
|
||||
...event,
|
||||
...eventData,
|
||||
};
|
||||
|
||||
eventsData.set(eventData.id, updatedEvent);
|
||||
|
||||
loggerData('updated', updatedEvent);
|
||||
|
||||
return response({ event: updatedEvent }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Update', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* PATCH - Delete event
|
||||
*************************************** */
|
||||
export async function PATCH(req: NextRequest) {
|
||||
try {
|
||||
const { eventId } = await req.json();
|
||||
|
||||
if (!eventsData.has(eventId)) {
|
||||
return response({ message: 'Event not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
eventsData.delete(eventId);
|
||||
|
||||
loggerData('deleted', eventId);
|
||||
|
||||
return response({ eventId }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Delete', error);
|
||||
}
|
||||
}
|
178
03_source/cms_backend/src/app/api/chat/route.ts
Normal file
178
03_source/cms_backend/src/app/api/chat/route.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _contacts, _conversations } from 'src/_mock/_chat';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
type ConversationType = ReturnType<typeof _conversations>[number];
|
||||
let conversationsData = new Map<string, ConversationType>();
|
||||
|
||||
const ENDPOINTS = {
|
||||
CONVERSATIONS: 'conversations',
|
||||
CONVERSATION: 'conversation',
|
||||
MARK_AS_SEEN: 'mark-as-seen',
|
||||
CONTACTS: 'contacts',
|
||||
};
|
||||
|
||||
function loggerData(action?: string, value?: unknown) {
|
||||
logger('[Chat] total-conversations', conversationsData.size);
|
||||
if (value || action) {
|
||||
logger(`[Chat] ${action}`, value);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeConversations() {
|
||||
if (conversationsData.size === 0) {
|
||||
const conversations = _conversations();
|
||||
conversationsData = new Map(conversations.map((conv) => [conv.id, conv]));
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* GET - Handle actions based on the endpoint
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const endpoint = searchParams.get('endpoint');
|
||||
|
||||
switch (endpoint) {
|
||||
case ENDPOINTS.CONVERSATIONS:
|
||||
return getConversations();
|
||||
case ENDPOINTS.CONVERSATION:
|
||||
return getConversation(req);
|
||||
case ENDPOINTS.MARK_AS_SEEN:
|
||||
return markAsSeen(req);
|
||||
case ENDPOINTS.CONTACTS:
|
||||
return getContacts();
|
||||
default:
|
||||
return response({ message: 'Endpoint not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
} catch (error) {
|
||||
return handleError(`Chat - Get request`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* POST - Create conversation
|
||||
*************************************** */
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { conversationData } = await req.json();
|
||||
|
||||
conversationsData.set(conversationData.id, conversationData);
|
||||
|
||||
loggerData('created-conversation', conversationData.id);
|
||||
|
||||
return response({ conversation: conversationData }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Chat - Create conversation', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* PUT - Update conversation
|
||||
*************************************** */
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const { conversationId, messageData } = await req.json();
|
||||
|
||||
const conversation = conversationsData.get(conversationId);
|
||||
|
||||
if (!conversation) {
|
||||
return response({ message: 'Conversation not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
const updatedConversation = {
|
||||
...conversation,
|
||||
messages: [...conversation.messages, messageData],
|
||||
};
|
||||
|
||||
conversationsData.set(conversationId, updatedConversation);
|
||||
|
||||
loggerData('updated-conversation', conversationId);
|
||||
|
||||
return response({ conversation: updatedConversation }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Chat - Update conversation', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* GET - Contact list
|
||||
*************************************** */
|
||||
async function getContacts() {
|
||||
return response({ contacts: _contacts() }, STATUS.OK);
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* GET - Conversation list
|
||||
*************************************** */
|
||||
async function getConversations() {
|
||||
try {
|
||||
initializeConversations();
|
||||
|
||||
loggerData();
|
||||
|
||||
return response({ conversations: Array.from(conversationsData.values()) }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Chat - Get conversations', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* GET - Conversation
|
||||
*************************************** */
|
||||
async function getConversation(req: NextRequest) {
|
||||
initializeConversations(); // Fix when the conversationsData is empty
|
||||
|
||||
const { searchParams } = req.nextUrl;
|
||||
const conversationId = searchParams.get('conversationId');
|
||||
|
||||
if (!conversationId) {
|
||||
return response({ message: 'Missing conversation id!' }, STATUS.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const conversation = conversationsData.get(conversationId);
|
||||
|
||||
if (!conversation) {
|
||||
return response({ message: 'Conversation not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
loggerData('get-conversation', conversation.id);
|
||||
|
||||
return response({ conversation }, STATUS.OK);
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* PUT - Mark conversation as seen
|
||||
*************************************** */
|
||||
async function markAsSeen(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const conversationId = searchParams.get('conversationId');
|
||||
|
||||
if (!conversationId) {
|
||||
return response({ message: 'Missing conversation id!' }, STATUS.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const conversation = conversationsData.get(conversationId);
|
||||
|
||||
if (!conversation) {
|
||||
return response({ message: 'Conversation not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
const updatedConversation = {
|
||||
...conversation,
|
||||
unreadCount: 0,
|
||||
};
|
||||
conversationsData.set(conversationId, updatedConversation);
|
||||
|
||||
loggerData('conversation-marked-as-seen', conversation.id);
|
||||
|
||||
return response({ conversationId }, STATUS.OK);
|
||||
}
|
16
03_source/cms_backend/src/app/api/helloworld/list/route.ts
Normal file
16
03_source/cms_backend/src/app/api/helloworld/list/route.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
const products = await prisma.productItem.findMany();
|
||||
console.log({ products });
|
||||
|
||||
return response({ products }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get latest', error);
|
||||
}
|
||||
}
|
16
03_source/cms_backend/src/app/api/helloworld/route.ts
Normal file
16
03_source/cms_backend/src/app/api/helloworld/route.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import prisma from '../../lib/prisma';
|
||||
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
const users = await prisma.user.findMany();
|
||||
console.log({ users });
|
||||
|
||||
return response({ users }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get latest', error);
|
||||
}
|
||||
}
|
271
03_source/cms_backend/src/app/api/kanban/route.ts
Normal file
271
03_source/cms_backend/src/app/api/kanban/route.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _board } from 'src/_mock/_kanban';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
type BoardType = ReturnType<typeof _board>;
|
||||
let boardData: BoardType = _board();
|
||||
|
||||
const ENDPOINTS = {
|
||||
CREATE_COLUMN: 'create-column',
|
||||
UPDATE_COLUMN: 'update-column',
|
||||
MOVE_COLUMN: 'move-column',
|
||||
CLEAR_COLUMN: 'clear-column',
|
||||
DELETE_COLUMN: 'delete-column',
|
||||
CREATE_TASK: 'create-task',
|
||||
UPDATE_TASK: 'update-task',
|
||||
MOVE_TASK: 'move-task',
|
||||
DELETE_TASK: 'delete-task',
|
||||
};
|
||||
|
||||
function loggerData(action?: string, value?: unknown) {
|
||||
const columnsWithTasks = boardData.columns.map(
|
||||
(col) => `${col.name} (${boardData.tasks[col.id].length} tasks)`
|
||||
);
|
||||
logger(
|
||||
'[Kanban] get-board',
|
||||
`columns (${boardData.columns.length}): ${JSON.stringify(columnsWithTasks, null, 2)}`
|
||||
);
|
||||
if (value || action) {
|
||||
logger(`[Kanban] ${action}`, value);
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoardData(newData: Partial<BoardType>) {
|
||||
boardData = { ...boardData, ...newData };
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* GET - Board
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
loggerData();
|
||||
|
||||
return response({ board: boardData }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Kanban - Get board', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* POST - Handle actions based on the endpoint
|
||||
*************************************** */
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const endpoint = searchParams.get('endpoint');
|
||||
|
||||
switch (endpoint) {
|
||||
case ENDPOINTS.CREATE_COLUMN:
|
||||
return createColumn(req);
|
||||
case ENDPOINTS.UPDATE_COLUMN:
|
||||
return updateColumn(req);
|
||||
case ENDPOINTS.MOVE_COLUMN:
|
||||
return moveColumn(req);
|
||||
case ENDPOINTS.CLEAR_COLUMN:
|
||||
return clearColumn(req);
|
||||
case ENDPOINTS.DELETE_COLUMN:
|
||||
return deleteColumn(req);
|
||||
case ENDPOINTS.CREATE_TASK:
|
||||
return createTask(req);
|
||||
case ENDPOINTS.UPDATE_TASK:
|
||||
return updateTask(req);
|
||||
case ENDPOINTS.MOVE_TASK:
|
||||
return moveTask(req);
|
||||
case ENDPOINTS.DELETE_TASK:
|
||||
return deleteTask(req);
|
||||
default:
|
||||
return response({ message: 'Endpoint not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
} catch (error) {
|
||||
return handleError(`Kanban - Post request`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* COLUMN MANAGEMENT
|
||||
*************************************** */
|
||||
|
||||
/**
|
||||
* @Column Create
|
||||
* Create a new column in the board.
|
||||
*/
|
||||
async function createColumn(req: NextRequest) {
|
||||
const { columnData } = await req.json();
|
||||
|
||||
// Add the new column and initialize its task list
|
||||
updateBoardData({
|
||||
columns: [...boardData.columns, columnData],
|
||||
tasks: { ...boardData.tasks, [columnData.id]: [] },
|
||||
});
|
||||
|
||||
loggerData('created-column', columnData.name);
|
||||
|
||||
return response({ column: columnData }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Column Update
|
||||
* Update the name of an existing column.
|
||||
*/
|
||||
async function updateColumn(req: NextRequest) {
|
||||
const { columnId, columnName } = await req.json();
|
||||
|
||||
const column = boardData.columns.find((col) => col.id === columnId);
|
||||
|
||||
if (!column) {
|
||||
return response({ message: 'Column not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Find and update the specified column.
|
||||
updateBoardData({
|
||||
columns: boardData.columns.map((col) =>
|
||||
col.id === columnId ? { ...col, name: columnName } : col
|
||||
),
|
||||
});
|
||||
|
||||
loggerData('updated-column', columnName);
|
||||
return response({ columnId, columnName }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Column Move
|
||||
* Reorder columns in the board.
|
||||
*/
|
||||
async function moveColumn(req: NextRequest) {
|
||||
const { updateColumns } = await req.json();
|
||||
|
||||
// Update the column order
|
||||
updateBoardData({
|
||||
columns: updateColumns,
|
||||
});
|
||||
|
||||
loggerData('moved-column', 'success!');
|
||||
|
||||
return response({ columns: updateColumns }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Column Clear
|
||||
* Remove all tasks from a specific column.
|
||||
*/
|
||||
async function clearColumn(req: NextRequest) {
|
||||
const { columnId } = await req.json();
|
||||
|
||||
// Clear tasks for the specified column
|
||||
updateBoardData({
|
||||
tasks: { ...boardData.tasks, [columnId]: [] },
|
||||
});
|
||||
|
||||
loggerData('cleared-column', 'success!');
|
||||
|
||||
return response({ columnId }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Column Delete
|
||||
* Delete a column and its associated tasks.
|
||||
*/
|
||||
async function deleteColumn(req: NextRequest) {
|
||||
const { columnId } = await req.json();
|
||||
|
||||
// Remove the column and its tasks
|
||||
updateBoardData({
|
||||
columns: boardData.columns.filter((col) => col.id !== columnId),
|
||||
tasks: Object.fromEntries(Object.entries(boardData.tasks).filter(([id]) => id !== columnId)),
|
||||
});
|
||||
|
||||
loggerData('deleted-column', columnId);
|
||||
|
||||
return response({ columnId }, STATUS.OK);
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* TASK MANAGEMENT
|
||||
*************************************** */
|
||||
|
||||
/**
|
||||
* @Task Create
|
||||
* Add a new task to a specific column.
|
||||
*/
|
||||
async function createTask(req: NextRequest) {
|
||||
const { columnId, taskData } = await req.json();
|
||||
|
||||
// Add the new task to the specified column
|
||||
updateBoardData({
|
||||
tasks: {
|
||||
...boardData.tasks,
|
||||
[columnId]: [taskData, ...boardData.tasks[columnId]],
|
||||
},
|
||||
});
|
||||
|
||||
loggerData('created-task', taskData.name);
|
||||
|
||||
return response({ columnId, taskData }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Task Update
|
||||
* Update an existing task in a specific column.
|
||||
*/
|
||||
async function updateTask(req: NextRequest) {
|
||||
const { columnId, taskData } = await req.json();
|
||||
|
||||
// Update the task in the specified column
|
||||
updateBoardData({
|
||||
tasks: {
|
||||
...boardData.tasks,
|
||||
[columnId]: boardData.tasks[columnId].map((task) =>
|
||||
task.id === taskData.id ? { ...task, ...taskData } : task
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
loggerData('updated-task', taskData.name);
|
||||
|
||||
return response({ task: taskData }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Task Move
|
||||
* Move a task between columns or reorder within the same column.
|
||||
*/
|
||||
async function moveTask(req: NextRequest) {
|
||||
const { updateTasks } = await req.json();
|
||||
|
||||
// Update the task structure
|
||||
updateBoardData({
|
||||
tasks: updateTasks,
|
||||
});
|
||||
|
||||
loggerData('moved-task', 'success!');
|
||||
|
||||
return response({ tasks: updateTasks }, STATUS.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Task Delete
|
||||
* Remove a task from a specific column.
|
||||
*/
|
||||
async function deleteTask(req: NextRequest) {
|
||||
const { columnId, taskId } = await req.json();
|
||||
|
||||
// Remove the task from the specified column
|
||||
updateBoardData({
|
||||
tasks: {
|
||||
...boardData.tasks,
|
||||
[columnId]: boardData.tasks[columnId].filter((task) => task.id !== taskId),
|
||||
},
|
||||
});
|
||||
|
||||
loggerData('deleted-task', taskId);
|
||||
|
||||
return response({ columnId, taskId }, STATUS.OK);
|
||||
}
|
34
03_source/cms_backend/src/app/api/mail/details/route.ts
Normal file
34
03_source/cms_backend/src/app/api/mail/details/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _mails } from 'src/_mock/_mail';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Mail details
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const mailId = searchParams.get('mailId');
|
||||
|
||||
const mails = _mails();
|
||||
|
||||
const mail = mails.find((mailItem) => mailItem.id === mailId);
|
||||
|
||||
if (!mail) {
|
||||
return response({ message: 'Mail not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
logger('[Mail] details', mail.id);
|
||||
|
||||
return response({ mail }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Mail - Get details', error);
|
||||
}
|
||||
}
|
23
03_source/cms_backend/src/app/api/mail/labels/route.ts
Normal file
23
03_source/cms_backend/src/app/api/mail/labels/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _labels } from 'src/_mock/_mail';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Labels
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const labels = _labels();
|
||||
|
||||
logger('[Mail] labels', labels.length);
|
||||
|
||||
return response({ labels }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Mail - Get labels', error);
|
||||
}
|
||||
}
|
57
03_source/cms_backend/src/app/api/mail/list/route.ts
Normal file
57
03_source/cms_backend/src/app/api/mail/list/route.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _mails, _labels } from 'src/_mock/_mail';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
type MailType = ReturnType<typeof _mails>[number];
|
||||
|
||||
/** **************************************
|
||||
* GET - Mails by labelId
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const labelId = searchParams.get('labelId');
|
||||
|
||||
const labels = _labels();
|
||||
const mails = _mails();
|
||||
|
||||
logger('[Mail] labelId', labelId);
|
||||
|
||||
const label = labels.find((labelItem) => labelItem.id === labelId);
|
||||
|
||||
if (!label) {
|
||||
return response({ message: 'Label not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Get filtered mails
|
||||
const filteredMails =
|
||||
label.type === 'custom'
|
||||
? mails.filter((mail) => mail.labelIds.includes(labelId!))
|
||||
: filterMailsByLabelId(mails, labelId);
|
||||
|
||||
logger(`[Mail] label-[${labelId}]`, filteredMails.length);
|
||||
|
||||
return response({ mails: filteredMails }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Mail - Get list', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** **************************************
|
||||
* Actions & Utility
|
||||
*************************************** */
|
||||
function filterMailsByLabelId(mails: MailType[], labelId?: string | null) {
|
||||
if (!labelId || labelId === 'inbox') return mails.filter((mail) => mail.folder === 'inbox');
|
||||
if (labelId === 'all') return mails;
|
||||
if (labelId === 'starred') return mails.filter((mail) => mail.isStarred);
|
||||
if (labelId === 'important') return mails.filter((mail) => mail.isImportant);
|
||||
|
||||
return mails.filter((mail) => mail.folder === labelId);
|
||||
}
|
21
03_source/cms_backend/src/app/api/navbar/route.ts
Normal file
21
03_source/cms_backend/src/app/api/navbar/route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _navItems } from 'src/_mock/_navbar';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Nav items
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
logger('[Nav] items', _navItems.length);
|
||||
|
||||
return response({ navItems: _navItems }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Nav - Get list', error);
|
||||
}
|
||||
}
|
80
03_source/cms_backend/src/app/api/pagination/route.ts
Normal file
80
03_source/cms_backend/src/app/api/pagination/route.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const DEFAULT_PER_PAGE = 10;
|
||||
const TOTAL_PRODUCTS = 50;
|
||||
|
||||
const _products = Array.from({ length: TOTAL_PRODUCTS }, (_, index) => ({
|
||||
id: `id-${index + 1}`,
|
||||
name: `product-${index + 1}`,
|
||||
category: (index % 2 && 'Accessories') || (index % 3 && 'Shoes') || 'Clothing',
|
||||
}));
|
||||
|
||||
type Products = typeof _products;
|
||||
|
||||
/** **************************************
|
||||
* Products with pagination and filters
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
const pageParam = searchParams.get('page') ?? `${DEFAULT_PAGE}`;
|
||||
const perPageParam = searchParams.get('perPage') ?? `${DEFAULT_PER_PAGE}`;
|
||||
|
||||
const page = parseInt(pageParam, 10);
|
||||
const perPage = parseInt(perPageParam, 10);
|
||||
const searchQuery = searchParams.get('search')?.trim().toLowerCase() ?? '';
|
||||
const category = searchParams.get('category')?.trim() ?? '';
|
||||
|
||||
try {
|
||||
const filteredProducts = filterProducts(_products, searchQuery, category);
|
||||
const paginatedProducts = paginateProducts(filteredProducts, page, perPage);
|
||||
|
||||
const totalPages = Math.ceil(filteredProducts.length / perPage);
|
||||
const totalItems = filteredProducts.length;
|
||||
|
||||
logger('[Product] filtered-products', filteredProducts.length);
|
||||
|
||||
return response(
|
||||
{
|
||||
products: paginatedProducts,
|
||||
totalPages,
|
||||
totalItems,
|
||||
categoryOptions: Array.from(
|
||||
new Set(_products.map(({ category: c_category }) => c_category))
|
||||
), // Remove duplicate categories
|
||||
},
|
||||
STATUS.OK
|
||||
);
|
||||
} catch (error) {
|
||||
return handleError('Pagination - Get list of products', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
function paginateProducts(products: Products, page: number, perPage: number) {
|
||||
const startIndex = (page - 1) * perPage;
|
||||
const endIndex = startIndex + perPage;
|
||||
|
||||
return products.slice(startIndex, endIndex);
|
||||
}
|
||||
|
||||
function filterProducts(products: Products, searchQuery: string, category: string) {
|
||||
return products.filter(({ id, name, category: prodCategory }) => {
|
||||
// Accept search by id or name
|
||||
const matchesSearch = searchQuery
|
||||
? id.includes(searchQuery) || name.toLowerCase().includes(searchQuery)
|
||||
: true;
|
||||
const matchesCategory = category ? prodCategory === category : true;
|
||||
|
||||
return matchesSearch && matchesCategory;
|
||||
});
|
||||
}
|
36
03_source/cms_backend/src/app/api/post/details/route.ts
Normal file
36
03_source/cms_backend/src/app/api/post/details/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { kebabCase } from 'es-toolkit';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _posts } from 'src/_mock/_blog';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* Get post details
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const title = searchParams.get('title');
|
||||
|
||||
const posts = _posts();
|
||||
|
||||
const post = posts.find((postItem) => kebabCase(postItem.title) === title);
|
||||
|
||||
if (!post) {
|
||||
return response({ message: 'Post not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
logger('[Post] details', post.id);
|
||||
|
||||
return response({ post }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get details', error);
|
||||
}
|
||||
}
|
32
03_source/cms_backend/src/app/api/post/latest/route.ts
Normal file
32
03_source/cms_backend/src/app/api/post/latest/route.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { kebabCase } from 'es-toolkit';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _posts } from 'src/_mock/_blog';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* Get latest posts
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const title = searchParams.get('title');
|
||||
|
||||
const posts = _posts();
|
||||
|
||||
const latestPosts = posts.filter((_post) => kebabCase(_post.title) !== title);
|
||||
|
||||
logger('[Post] latest-list', latestPosts.length);
|
||||
|
||||
return response({ latestPosts }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get latest', error);
|
||||
}
|
||||
}
|
23
03_source/cms_backend/src/app/api/post/list/route.ts
Normal file
23
03_source/cms_backend/src/app/api/post/list/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _posts } from 'src/_mock/_blog';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Posts
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const posts = _posts();
|
||||
|
||||
logger('[Post] list', posts.length);
|
||||
|
||||
return response({ posts }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get list', error);
|
||||
}
|
||||
}
|
38
03_source/cms_backend/src/app/api/post/search/route.ts
Normal file
38
03_source/cms_backend/src/app/api/post/search/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _posts } from 'src/_mock/_blog';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Search posts
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const query = searchParams.get('query')?.trim().toLowerCase();
|
||||
|
||||
if (!query) {
|
||||
return response({ results: [] }, STATUS.OK);
|
||||
}
|
||||
|
||||
const posts = _posts();
|
||||
|
||||
// Accept search by title or description
|
||||
const results = posts.filter(
|
||||
({ title, description }) =>
|
||||
title.toLowerCase().includes(query) || description?.toLowerCase().includes(query)
|
||||
);
|
||||
|
||||
logger('[Post] search-results', results.length);
|
||||
|
||||
return response({ results }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Post - Get search', error);
|
||||
}
|
||||
}
|
34
03_source/cms_backend/src/app/api/product/details/route.ts
Normal file
34
03_source/cms_backend/src/app/api/product/details/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _products } from 'src/_mock/_product';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* Get product details
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const productId = searchParams.get('productId');
|
||||
|
||||
const products = _products();
|
||||
|
||||
const product = products.find((productItem) => productItem.id === productId);
|
||||
|
||||
if (!product) {
|
||||
return response({ message: 'Product not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
logger('[Product] details', product.id);
|
||||
|
||||
return response({ product }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get details', error);
|
||||
}
|
||||
}
|
21
03_source/cms_backend/src/app/api/product/list/route.ts
Normal file
21
03_source/cms_backend/src/app/api/product/list/route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Products
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const products = await prisma.productItem.findMany();
|
||||
|
||||
logger('[Product] list', products.length);
|
||||
|
||||
return response({ products }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get list', error);
|
||||
}
|
||||
}
|
23
03_source/cms_backend/src/app/api/product/list/route.ts.bak
Normal file
23
03_source/cms_backend/src/app/api/product/list/route.ts.bak
Normal file
@@ -0,0 +1,23 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _products } from 'src/_mock/_product';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Products
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const products = _products();
|
||||
|
||||
logger('[Product] list', products.length);
|
||||
|
||||
return response({ products }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get list', error);
|
||||
}
|
||||
}
|
37
03_source/cms_backend/src/app/api/product/search/route.ts
Normal file
37
03_source/cms_backend/src/app/api/product/search/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _products } from 'src/_mock/_product';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Search products
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const query = searchParams.get('query')?.trim().toLowerCase();
|
||||
|
||||
if (!query) {
|
||||
return response({ results: [] }, STATUS.OK);
|
||||
}
|
||||
|
||||
const products = _products();
|
||||
|
||||
// Accept search by name or sku
|
||||
const results = products.filter(
|
||||
({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query)
|
||||
);
|
||||
|
||||
logger('[Product] search-results', results.length);
|
||||
|
||||
return response({ results }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get search', error);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user