From 35632f87b8b3d9737ffe963edffac66c7959f79d Mon Sep 17 00:00:00 2001 From: cawcenter Date: Tue, 16 Dec 2025 12:14:08 -0500 Subject: [PATCH] Add page management system: shim layer + API routes for pages/posts/sites CRUD --- src/lib/shim/pages.ts | 230 +++++++++++++++++++++++++++++ src/lib/shim/posts.ts | 227 ++++++++++++++++++++++++++++ src/lib/shim/schemas.ts | 21 +++ src/pages/api/shim/pages/[id].ts | 92 ++++++++++++ src/pages/api/shim/pages/create.ts | 37 +++++ src/pages/api/shim/pages/list.ts | 44 ++++++ src/pages/api/shim/posts/[id].ts | 92 ++++++++++++ src/pages/api/shim/posts/create.ts | 37 +++++ src/pages/api/shim/posts/list.ts | 44 ++++++ src/pages/api/shim/sites/[id].ts | 35 +++++ 10 files changed, 859 insertions(+) create mode 100644 src/lib/shim/pages.ts create mode 100644 src/lib/shim/posts.ts create mode 100644 src/pages/api/shim/pages/[id].ts create mode 100644 src/pages/api/shim/pages/create.ts create mode 100644 src/pages/api/shim/pages/list.ts create mode 100644 src/pages/api/shim/posts/[id].ts create mode 100644 src/pages/api/shim/posts/create.ts create mode 100644 src/pages/api/shim/posts/list.ts create mode 100644 src/pages/api/shim/sites/[id].ts diff --git a/src/lib/shim/pages.ts b/src/lib/shim/pages.ts new file mode 100644 index 0000000..75cc233 --- /dev/null +++ b/src/lib/shim/pages.ts @@ -0,0 +1,230 @@ +// Pages table CRUD operations +// Manages static landing pages with routes + +import { pool } from '@/lib/db'; +import type { FilterOptions, PaginationResult } from './types'; +import { buildWhere, buildSearch, buildPagination, buildUpdateSet, getSingleResult, isValidUUID } from './utils'; + +export interface Page { + id: string; + site_id: string; + name: string; + route: string; + html_content: string | null; + meta_title: string | null; + meta_description: string | null; + status: string; + published_at: Date | null; + created_at: Date; + updated_at: Date; +} + +/** + * Get all pages with filtering and pagination + */ +export async function getPages(options: FilterOptions = {}): Promise> { + const { limit = 50, offset = 0, status, search, siteId } = options; + + let sql = 'SELECT * FROM pages WHERE 1=1'; + const params: any[] = []; + let paramIndex = 1; + + if (status) { + sql += ` AND status = $${paramIndex++}`; + params.push(status); + } + + if (siteId) { + sql += ` AND site_id = $${paramIndex++}`; + params.push(siteId); + } + + if (search) { + const [searchSql, searchParam] = buildSearch('name', search, paramIndex++); + sql += searchSql; + params.push(searchParam); + } + + const [paginationSql, safeLimit, safeOffset] = buildPagination(limit, offset, paramIndex); + sql += ' ORDER BY created_at DESC' + paginationSql; + params.push(safeLimit, safeOffset); + + const { rows } = await pool.query(sql, params); + + // Get total count + const countSql = 'SELECT COUNT(*) FROM pages WHERE 1=1' + + (status ? ` AND status = $1` : '') + + (siteId ? ` AND site_id = $${status ? 2 : 1}` : ''); + const countParams = [status, siteId].filter(Boolean); + const { rows: countRows } = await pool.query<{ count: string }>(countSql, countParams); + const total = parseInt(countRows[0]?.count || '0'); + + return { + data: rows, + total, + limit: safeLimit, + offset: safeOffset, + hasMore: safeOffset + rows.length < total + }; +} + +/** + * Get single page by ID + */ +export async function getPageById(id: string): Promise { + if (!isValidUUID(id)) { + throw new Error('Invalid page ID format'); + } + + const { rows } = await pool.query( + 'SELECT * FROM pages WHERE id = $1', + [id] + ); + return getSingleResult(rows); +} + +/** + * Get page by site and route + */ +export async function getPageByRoute(siteId: string, route: string): Promise { + if (!isValidUUID(siteId)) { + throw new Error('Invalid site ID format'); + } + + const { rows } = await pool.query( + 'SELECT * FROM pages WHERE site_id = $1 AND route = $2', + [siteId, route] + ); + return getSingleResult(rows); +} + +/** + * Get pages by site + */ +export async function getPagesBySite(siteId: string, options: FilterOptions = {}): Promise { + if (!isValidUUID(siteId)) { + throw new Error('Invalid site ID format'); + } + + const { limit = 50, offset = 0, status } = options; + + let sql = 'SELECT * FROM pages WHERE site_id = $1'; + const params: any[] = [siteId]; + let paramIndex = 2; + + if (status) { + sql += ` AND status = $${paramIndex++}`; + params.push(status); + } + + const [paginationSql, safeLimit, safeOffset] = buildPagination(limit, offset, paramIndex); + sql += ' ORDER BY created_at DESC' + paginationSql; + params.push(safeLimit, safeOffset); + + const { rows } = await pool.query(sql, params); + return rows; +} + +/** + * Create new page with validation + */ +export async function createPage(data: unknown): Promise { + // Import Zod schema + const { PageSchema, validateForCreate } = await import('./schemas'); + + // Validate input + const validatedData = validateForCreate(PageSchema, data, 'Page'); + + const { rows } = await pool.query( + `INSERT INTO pages (site_id, name, route, html_content, meta_title, meta_description, status) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING *`, + [ + validatedData.site_id, + validatedData.name, + validatedData.route, + validatedData.html_content || '', + validatedData.meta_title || '', + validatedData.meta_description || '', + validatedData.status || 'draft' + ] + ); + + if (rows.length === 0) { + throw new Error('Failed to create page'); + } + + return rows[0]; +} + +/** + * Update existing page + */ +export async function updatePage(id: string, data: unknown): Promise { + if (!isValidUUID(id)) { + throw new Error('Invalid page ID format'); + } + + const { PartialPageSchema, validateForUpdate } = await import('./schemas'); + + const validatedData = validateForUpdate( + PartialPageSchema, + { ...(data as Record), id }, + 'Page' + ); + + const [setClause, values] = buildUpdateSet(validatedData); + values.push(id); + + const { rows } = await pool.query( + `UPDATE pages SET ${setClause}, updated_at = NOW() + WHERE id = $${values.length} + RETURNING *`, + values + ); + + if (rows.length === 0) { + throw new Error('Page not found'); + } + + return rows[0]; +} + +/** + * Delete page + */ +export async function deletePage(id: string): Promise { + if (!isValidUUID(id)) { + throw new Error('Invalid page ID format'); + } + + const result = await pool.query('DELETE FROM pages WHERE id = $1', [id]); + return result.rowCount ? result.rowCount > 0 : false; +} + +/** + * Publish page + */ +export async function publishPage(id: string): Promise { + return updatePage(id, { + status: 'published', + published_at: new Date() + }); +} + +/** + * Get pages count by status + */ +export async function getPagesCountByStatus(siteId?: string): Promise> { + const sql = siteId + ? 'SELECT status, COUNT(*) as count FROM pages WHERE site_id = $1 GROUP BY status' + : 'SELECT status, COUNT(*) as count FROM pages GROUP BY status'; + + const params = siteId ? [siteId] : []; + const { rows } = await pool.query<{ status: string; count: string }>(sql, params); + + return rows.reduce((acc, row) => { + acc[row.status] = parseInt(row.count); + return acc; + }, {} as Record); +} diff --git a/src/lib/shim/posts.ts b/src/lib/shim/posts.ts new file mode 100644 index 0000000..2174cb2 --- /dev/null +++ b/src/lib/shim/posts.ts @@ -0,0 +1,227 @@ +// Posts table CRUD operations (using existing posts table) +// Manages blog posts with SEO + +import { pool } from '@/lib/db'; +import type { FilterOptions, PaginationResult } from './types'; +import { buildWhere, buildSearch, buildPagination, buildUpdateSet, getSingleResult, isValidUUID } from './utils'; + +export interface Post { + id: string; + site_id: string; + title: string; + slug: string; + content: string | null; + excerpt: string | null; + status: string; + published_at: Date | null; + meta_title: string | null; + meta_description: string | null; + created_at: Date; + updated_at: Date; +} + +/** + * Get all posts with filtering and pagination + */ +export async function getPosts(options: FilterOptions = {}): Promise> { + const { limit = 50, offset = 0, status, search, siteId } = options; + + let sql = 'SELECT * FROM posts WHERE 1=1'; + const params: any[] = []; + let paramIndex = 1; + + if (status) { + sql += ` AND status = $${paramIndex++}`; + params.push(status); + } + + if (siteId) { + sql += ` AND site_id = $${paramIndex++}`; + params.push(siteId); + } + + if (search) { + const [searchSql, searchParam] = buildSearch('title', search, paramIndex++); + sql += searchSql; + params.push(searchParam); + } + + const [paginationSql, safeLimit, safeOffset] = buildPagination(limit, offset, paramIndex); + sql += ' ORDER BY created_at DESC' + paginationSql; + params.push(safeLimit, safeOffset); + + const { rows } = await pool.query(sql, params); + + // Get total count + const countSql = 'SELECT COUNT(*) FROM posts WHERE 1=1' + + (status ? ` AND status = $1` : '') + + (siteId ? ` AND site_id = $${status ? 2 : 1}` : ''); + const countParams = [status, siteId].filter(Boolean); + const { rows: countRows } = await pool.query<{ count: string }>(countSql, countParams); + const total = parseInt(countRows[0]?.count || '0'); + + return { + data: rows, + total, + limit: safeLimit, + offset: safeOffset, + hasMore: safeOffset + rows.length < total + }; +} + +/** + * Get single post by ID + */ +export async function getPostById(id: string): Promise { + if (!isValidUUID(id)) { + throw new Error('Invalid post ID format'); + } + + const { rows } = await pool.query( + 'SELECT * FROM posts WHERE id = $1', + [id] + ); + return getSingleResult(rows); +} + +/** + * Get post by site and slug + */ +export async function getPostBySlug(siteId: string, slug: string): Promise { + if (!isValidUUID(siteId)) { + throw new Error('Invalid site ID format'); + } + + const { rows } = await pool.query( + 'SELECT * FROM posts WHERE site_id = $1 AND slug = $2', + [siteId, slug] + ); + return getSingleResult(rows); +} + +/** + * Get posts by site + */ +export async function getPostsBySite(siteId: string, options: FilterOptions = {}): Promise { + if (!isValidUUID(siteId)) { + throw new Error('Invalid site ID format'); + } + + const { limit = 50, offset = 0, status } = options; + + let sql = 'SELECT * FROM posts WHERE site_id = $1'; + const params: any[] = [siteId]; + let paramIndex = 2; + + if (status) { + sql += ` AND status = $${paramIndex++}`; + params.push(status); + } + + const [paginationSql, safeLimit, safeOffset] = buildPagination(limit, offset, paramIndex); + sql += ' ORDER BY created_at DESC' + paginationSql; + params.push(safeLimit, safeOffset); + + const { rows } = await pool.query(sql, params); + return rows; +} + +/** + * Create new post (simplified - no SEO requirement) + */ +export async function createPost(data: { + site_id: string; + title: string; + slug: string; + content: string; + excerpt?: string; + meta_title?: string; + meta_description?: string; + status?: string; +}): Promise { + const { rows } = await pool.query( + `INSERT INTO posts (site_id, title, slug, content, excerpt, meta_title, meta_description, status) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING *`, + [ + data.site_id, + data.title, + data.slug, + data.content, + data.excerpt || '', + data.meta_title || data.title, + data.meta_description || '', + data.status || 'draft' + ] + ); + + if (rows.length === 0) { + throw new Error('Failed to create post'); + } + + return rows[0]; +} + +/** + * Update existing post + */ +export async function updatePost(id: string, data: Partial): Promise { + if (!isValidUUID(id)) { + throw new Error('Invalid post ID format'); + } + + const [setClause, values] = buildUpdateSet(data); + values.push(id); + + const { rows } = await pool.query( + `UPDATE posts SET ${setClause}, updated_at = NOW() + WHERE id = $${values.length} + RETURNING *`, + values + ); + + if (rows.length === 0) { + throw new Error('Post not found'); + } + + return rows[0]; +} + +/** + * Delete post + */ +export async function deletePost(id: string): Promise { + if (!isValidUUID(id)) { + throw new Error('Invalid post ID format'); + } + + const result = await pool.query('DELETE FROM posts WHERE id = $1', [id]); + return result.rowCount ? result.rowCount > 0 : false; +} + +/** + * Publish post + */ +export async function publishPost(id: string): Promise { + return updatePost(id, { + status: 'published', + published_at: new Date() + }); +} + +/** + * Get posts count by status + */ +export async function getPostsCountByStatus(siteId?: string): Promise> { + const sql = siteId + ? 'SELECT status, COUNT(*) as count FROM posts WHERE site_id = $1 GROUP BY status' + : 'SELECT status, COUNT(*) as count FROM posts GROUP BY status'; + + const params = siteId ? [siteId] : []; + const { rows } = await pool.query<{ status: string; count: string }>(sql, params); + + return rows.reduce((acc, row) => { + acc[row.status] = parseInt(row.count); + return acc; + }, {} as Record); +} diff --git a/src/lib/shim/schemas.ts b/src/lib/shim/schemas.ts index b0e3fac..6e38e09 100644 --- a/src/lib/shim/schemas.ts +++ b/src/lib/shim/schemas.ts @@ -99,6 +99,26 @@ export const CampaignSchema = z.object({ export type CampaignInput = z.infer; +/** + * PAGES SCHEMA + * For static landing pages with routes + */ +export const PageSchema = z.object({ + id: z.string().uuid().optional(), + site_id: z.string().uuid("Invalid site_id"), + name: z.string().min(1, "Name required").max(255, "Name too long"), + route: z.string() + .min(1, "Route required") + .regex(/^\/[a-z0-9-\/]*$/, "Route must start with / and contain only lowercase letters, numbers, hyphens, and slashes"), + html_content: z.string().optional(), + meta_title: z.string().max(70, "Meta title too long").optional(), + meta_description: z.string().max(160, "Meta description too long").optional(), + status: z.enum(['draft', 'published', 'archived']).default('draft'), + published_at: z.date().optional(), +}); + +export type PageInput = z.infer; + /** * GENERATION JOB SCHEMA */ @@ -122,6 +142,7 @@ export type GenerationJobInput = z.infer; export const PartialSiteSchema = SiteSchema.partial().required({ id: true }); export const PartialArticleSchema = ArticleSchema.partial().required({ id: true }); export const PartialCampaignSchema = CampaignSchema.partial().required({ id: true }); +export const PartialPageSchema = PageSchema.partial().required({ id: true }); /** * QUERY FILTER SCHEMAS diff --git a/src/pages/api/shim/pages/[id].ts b/src/pages/api/shim/pages/[id].ts new file mode 100644 index 0000000..615df38 --- /dev/null +++ b/src/pages/api/shim/pages/[id].ts @@ -0,0 +1,92 @@ +// API: Update/Delete page by ID +import type { APIRoute } from 'astro'; +import { updatePage, deletePage, getPageById } from '@/lib/shim/pages'; + +export const GET: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 }); + } + + const page = await getPageById(params.id!); + + if (!page) { + return new Response(JSON.stringify({ error: 'Page not found' }), { status: 404 }); + } + + return new Response(JSON.stringify(page), { status: 200 }); + } catch (error: any) { + return new Response(JSON.stringify({ error: error.message }), { status: 500 }); + } +}; + +export const PUT: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const data = await request.json(); + const page = await updatePage(params.id!, data); + + return new Response(JSON.stringify(page), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + return new Response(JSON.stringify({ + error: 'Failed to update page', + message: error.message + }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } +}; + +export const DELETE: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const success = await deletePage(params.id!); + + if (!success) { + return new Response(JSON.stringify({ error: 'Page not found' }), { status: 404 }); + } + + return new Response(JSON.stringify({ success: true }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + return new Response(JSON.stringify({ + error: 'Failed to delete page', + message: error.message + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; diff --git a/src/pages/api/shim/pages/create.ts b/src/pages/api/shim/pages/create.ts new file mode 100644 index 0000000..8d29ae4 --- /dev/null +++ b/src/pages/api/shim/pages/create.ts @@ -0,0 +1,37 @@ +// API: Create new page +import type { APIRoute } from 'astro'; +import { createPage } from '@/lib/shim/pages'; + +export const POST: APIRoute = async ({ request }) => { + try { + // Token validation + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const data = await request.json(); + const page = await createPage(data); + + return new Response(JSON.stringify(page), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + console.error('Create page error:', error); + return new Response(JSON.stringify({ + error: 'Failed to create page', + message: error.message + }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } +}; diff --git a/src/pages/api/shim/pages/list.ts b/src/pages/api/shim/pages/list.ts new file mode 100644 index 0000000..a9bddef --- /dev/null +++ b/src/pages/api/shim/pages/list.ts @@ -0,0 +1,44 @@ +// API: List pages with filtering +import type { APIRoute } from 'astro'; +import { getPages } from '@/lib/shim/pages'; + +export const GET: APIRoute = async ({ request }) => { + try { + // Token validation + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Parse query parameters + const url = new URL(request.url); + const limit = parseInt(url.searchParams.get('limit') || '50'); + const offset = parseInt(url.searchParams.get('offset') || '0'); + const status = url.searchParams.get('status') || undefined; + const siteId = url.searchParams.get('siteId') || undefined; + const search = url.searchParams.get('search') || undefined; + + const result = await getPages({ limit, offset, status, siteId, search }); + + return new Response(JSON.stringify(result), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + console.error('Pages list error:', error); + return new Response(JSON.stringify({ + error: 'Failed to fetch pages', + message: error.message + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; diff --git a/src/pages/api/shim/posts/[id].ts b/src/pages/api/shim/posts/[id].ts new file mode 100644 index 0000000..ccc2641 --- /dev/null +++ b/src/pages/api/shim/posts/[id].ts @@ -0,0 +1,92 @@ +// API: Update/Delete post by ID +import type { APIRoute } from 'astro'; +import { updatePost, deletePost, getPostById } from '@/lib/shim/posts'; + +export const GET: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 }); + } + + const post = await getPostById(params.id!); + + if (!post) { + return new Response(JSON.stringify({ error: 'Post not found' }), { status: 404 }); + } + + return new Response(JSON.stringify(post), { status: 200 }); + } catch (error: any) { + return new Response(JSON.stringify({ error: error.message }), { status: 500 }); + } +}; + +export const PUT: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const data = await request.json(); + const post = await updatePost(params.id!, data); + + return new Response(JSON.stringify(post), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + return new Response(JSON.stringify({ + error: 'Failed to update post', + message: error.message + }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } +}; + +export const DELETE: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const success = await deletePost(params.id!); + + if (!success) { + return new Response(JSON.stringify({ error: 'Post not found' }), { status: 404 }); + } + + return new Response(JSON.stringify({ success: true }), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + return new Response(JSON.stringify({ + error: 'Failed to delete post', + message: error.message + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; diff --git a/src/pages/api/shim/posts/create.ts b/src/pages/api/shim/posts/create.ts new file mode 100644 index 0000000..18e5d75 --- /dev/null +++ b/src/pages/api/shim/posts/create.ts @@ -0,0 +1,37 @@ +// API: Create new post +import type { APIRoute } from 'astro'; +import { createPost } from '@/lib/shim/posts'; + +export const POST: APIRoute = async ({ request }) => { + try { + // Token validation + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const data = await request.json(); + const post = await createPost(data); + + return new Response(JSON.stringify(post), { + status: 201, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + console.error('Create post error:', error); + return new Response(JSON.stringify({ + error: 'Failed to create post', + message: error.message + }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } +}; diff --git a/src/pages/api/shim/posts/list.ts b/src/pages/api/shim/posts/list.ts new file mode 100644 index 0000000..2e6b128 --- /dev/null +++ b/src/pages/api/shim/posts/list.ts @@ -0,0 +1,44 @@ +// API: List posts with filtering +import type { APIRoute } from 'astro'; +import { getPosts } from '@/lib/shim/posts'; + +export const GET: APIRoute = async ({ request }) => { + try { + // Token validation + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Parse query parameters + const url = new URL(request.url); + const limit = parseInt(url.searchParams.get('limit') || '50'); + const offset = parseInt(url.searchParams.get('offset') || '0'); + const status = url.searchParams.get('status') || undefined; + const siteId = url.searchParams.get('siteId') || undefined; + const search = url.searchParams.get('search') || undefined; + + const result = await getPosts({ limit, offset, status, siteId, search }); + + return new Response(JSON.stringify(result), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + console.error('Posts list error:', error); + return new Response(JSON.stringify({ + error: 'Failed to fetch posts', + message: error.message + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } +}; diff --git a/src/pages/api/shim/sites/[id].ts b/src/pages/api/shim/sites/[id].ts new file mode 100644 index 0000000..7de2fb5 --- /dev/null +++ b/src/pages/api/shim/sites/[id].ts @@ -0,0 +1,35 @@ +// API: Update site by ID +import type { APIRoute } from 'astro'; +import { updateSite } from '@/lib/shim/sites'; + +export const PUT: APIRoute = async ({ params, request }) => { + try { + const authHeader = request.headers.get('Authorization'); + const token = authHeader?.replace('Bearer ', ''); + + const godToken = import.meta.env.GOD_MODE_TOKEN; + if (godToken && token !== godToken) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { 'Content-Type': 'application/json' } + }); + } + + const data = await request.json(); + const site = await updateSite(params.id!, data); + + return new Response(JSON.stringify(site), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); + + } catch (error: any) { + return new Response(JSON.stringify({ + error: 'Failed to update site', + message: error.message + }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } +};