From f1947ccb62f2142d6e00f598122b5083c049af4b Mon Sep 17 00:00:00 2001 From: gibbyb Date: Sat, 20 Jul 2024 20:39:29 -0500 Subject: [PATCH] Add legacy APIs so that I can migrate new app to techtracker.gibbyb.com and keep iOS app working --- src/app/api/history/route.ts | 23 ++++ src/app/api/technicians/route.ts | 33 ++++++ src/app/api/update_technicians/route.ts | 44 ++++++++ src/app/api/{ => v2}/get_employees/route.ts | 0 src/app/api/{ => v2}/update_status/route.ts | 0 src/components/ui/Table.tsx | 4 +- src/server/functions.ts | 111 ++++++++++++++++++++ 7 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 src/app/api/history/route.ts create mode 100644 src/app/api/technicians/route.ts create mode 100644 src/app/api/update_technicians/route.ts rename src/app/api/{ => v2}/get_employees/route.ts (100%) rename src/app/api/{ => v2}/update_status/route.ts (100%) diff --git a/src/app/api/history/route.ts b/src/app/api/history/route.ts new file mode 100644 index 0000000..c983f59 --- /dev/null +++ b/src/app/api/history/route.ts @@ -0,0 +1,23 @@ +"use server"; +import { NextResponse } from 'next/server'; +import { legacyGetHistory } from '~/server/functions'; + +export const GET = async (request: Request) => { + try { + const url = new URL(request.url); + const apiKey = url.searchParams.get('apikey'); + const page = Number(url.searchParams.get('page')) || 1; + + if (apiKey !== 'zAf4vYVN2pszrK') { + return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); + } + + const perPage = 50; // You can adjust the perPage value as needed + const historyData = await legacyGetHistory(page, perPage); + + return NextResponse.json(historyData, { status: 200 }); + } catch (error) { + console.error('Error fetching history data:', error); + return NextResponse.json({ message: 'Internal server error' }, { status: 500 }); + } +}; diff --git a/src/app/api/technicians/route.ts b/src/app/api/technicians/route.ts new file mode 100644 index 0000000..020e911 --- /dev/null +++ b/src/app/api/technicians/route.ts @@ -0,0 +1,33 @@ +"use server"; +import { NextResponse } from 'next/server'; +import { legacyGetEmployees } from '~/server/functions'; + +type Technician = { + name: string; + status: string; + updatedAt: Date; +}; + +export const GET = async (request: Request) => { + try { + const url = new URL(request.url); + const apiKey = url.searchParams.get('apikey'); + + if (apiKey !== 'zAf4vYVN2pszrK') { + return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); + } + + const employees = await legacyGetEmployees(); + + const formattedEmployees = employees.map((employee: Technician) => ({ + name: employee.name, + status: employee.status, + time: employee.updatedAt + })); + + return NextResponse.json(formattedEmployees, { status: 200 }); + } catch (error) { + console.error('Error fetching employees:', error); + return NextResponse.json({ message: 'Internal server error' }, { status: 500 }); + } +}; diff --git a/src/app/api/update_technicians/route.ts b/src/app/api/update_technicians/route.ts new file mode 100644 index 0000000..9ec3859 --- /dev/null +++ b/src/app/api/update_technicians/route.ts @@ -0,0 +1,44 @@ +// src/app/api/update_technicians/route.ts + +"use server"; +import { NextResponse } from 'next/server'; +import { legacyUpdateEmployeeStatusByName } from '~/server/functions'; + +interface Technician { + name: string; + status: string; +} + +// Type guard to check if an object is a Technician +const isTechnician = (technician: any): technician is Technician => { + return typeof technician.name === 'string' && typeof technician.status === 'string'; +}; + +export const POST = async (request: Request) => { + try { + const url = new URL(request.url); + const apiKey = url.searchParams.get('apikey'); + + if (apiKey !== 'zAf4vYVN2pszrK') { + return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); + } + + const { technicians } = await request.json() as { technicians: Technician[] }; + if (!Array.isArray(technicians) || technicians.length === 0) { + return NextResponse.json({ message: 'Invalid input: expecting an array of technicians.' }, { status: 400 }); + } + + for (const technician of technicians) { + if (!isTechnician(technician)) { + return NextResponse.json({ message: 'Invalid input: missing name or status for a technician.' }, { status: 400 }); + } + } + + await legacyUpdateEmployeeStatusByName(technicians); + + return NextResponse.json({ message: 'Technicians updated successfully.' }, { status: 200 }); + } catch (error) { + console.error('Error updating technicians:', error); + return NextResponse.json({ message: 'Internal server error' }, { status: 500 }); + } +}; diff --git a/src/app/api/get_employees/route.ts b/src/app/api/v2/get_employees/route.ts similarity index 100% rename from src/app/api/get_employees/route.ts rename to src/app/api/v2/get_employees/route.ts diff --git a/src/app/api/update_status/route.ts b/src/app/api/v2/update_status/route.ts similarity index 100% rename from src/app/api/update_status/route.ts rename to src/app/api/v2/update_status/route.ts diff --git a/src/components/ui/Table.tsx b/src/components/ui/Table.tsx index 8c515cc..571168f 100644 --- a/src/components/ui/Table.tsx +++ b/src/components/ui/Table.tsx @@ -22,7 +22,7 @@ export default function Table({ employees }: { employees: Employee[] }) { }, [employees]); const fetchEmployees = useCallback(async (): Promise => { - const res = await fetch('/api/get_employees', { + const res = await fetch('/api/v2/get_employees', { method: 'GET', headers: { 'Authorization': `Bearer ${process.env.API_KEY}` @@ -86,7 +86,7 @@ export default function Table({ employees }: { employees: Employee[] }) { const handleSubmit = async () => { if (selectedIds.length > 0 && status.trim() !== '') { - await fetch('/api/update_status', { + await fetch('/api/v2/update_status', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/server/functions.ts b/src/server/functions.ts index 9ecb4e7..7c08f69 100644 --- a/src/server/functions.ts +++ b/src/server/functions.ts @@ -32,3 +32,114 @@ export const updateEmployeeStatus = async (employeeIds: string[], newStatus: str throw new Error("Failed to update status"); } }; + +// Legacy Functions for Legacy API for iOS App + +// Type definitions +interface HistoryEntry { + name: string; + status: string; + time: Date; +} + +interface PaginatedHistory { + data: HistoryEntry[]; + meta: { + current_page: number; + per_page: number; + total_pages: number; + total_count: number; + } +} + +// Function to Convert Date to UTC +const convertToUTC = (date: Date): Date => { + const utcDate = new Date(date.setHours(date.getUTCHours() - 15)); + return utcDate; +} + +export const legacyGetEmployees = async () => { + const employees = await db.query.users.findMany({ + orderBy: (model, { asc }) => asc(model.id), + }); + if (employees.length === 0) { + return []; + } + for (const employee of employees) { + const date = new Date(employee.updatedAt); + employee.updatedAt = convertToUTC(date); + } + return employees; +}; + +// Function to Get History Data with Pagination using Raw SQL +export const legacyGetHistory = async (page: number, perPage: number): Promise => { + const offset = (page - 1) * perPage; + + // Raw SQL queries + const historyQuery = sql` + SELECT u.name, h.status, h.updatedAt + FROM history h + JOIN users u ON h.user_id = u.id + ORDER BY h.id DESC + LIMIT ${perPage} OFFSET ${offset} + `; + + const countQuery = sql` + SELECT COUNT(*) AS total_count + FROM history + `; + + const [historyResults, countResults] = await Promise.all([ + db.execute(historyQuery), + db.execute(countQuery), + ]); + + // Get the results properly as an array of rows + const historyRows = historyResults[0] as { name: string, status: string, updatedAt: Date }[]; + const countRow = countResults[0] as { total_count: number }[]; + + const totalCount = countRow[0]?.total_count || 0; + const totalPages = Math.ceil(totalCount / perPage); + + // Format and map results + const formattedResults: HistoryEntry[] = historyRows.map(row => ({ + name: row.name, + status: row.status, + time: convertToUTC(new Date(row.updatedAt)), + })); + + return { + data: formattedResults, + meta: { + current_page: page, + per_page: perPage, + total_pages: totalPages, + total_count: totalCount, + } + }; +}; + +// Function to Update Employee Status by Name using Raw SQL +export const legacyUpdateEmployeeStatusByName = async (technicians: { name: string, status: string }[]) => { + try { + // Prepare and execute the queries for each technician + for (const technician of technicians) { + const { name, status } = technician; + const date = new Date(); + const utcdate: Date = new Date(date.setHours(date.getUTCHours() - 12)); + const query = sql` + UPDATE users + SET status = ${status}, updatedAt = ${utcdate} + WHERE name = ${name} + `; + + await db.execute(query); + } + + return { success: true }; + } catch (error) { + console.error("Error updating employee status by name:", error); + throw new Error("Failed to update status by name"); + } +};