Add page management system: shim layer + API routes for pages/posts/sites CRUD
This commit is contained in:
230
src/lib/shim/pages.ts
Normal file
230
src/lib/shim/pages.ts
Normal file
@@ -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<PaginationResult<Page>> {
|
||||
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<Page>(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<Page | null> {
|
||||
if (!isValidUUID(id)) {
|
||||
throw new Error('Invalid page ID format');
|
||||
}
|
||||
|
||||
const { rows } = await pool.query<Page>(
|
||||
'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<Page | null> {
|
||||
if (!isValidUUID(siteId)) {
|
||||
throw new Error('Invalid site ID format');
|
||||
}
|
||||
|
||||
const { rows } = await pool.query<Page>(
|
||||
'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<Page[]> {
|
||||
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<Page>(sql, params);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new page with validation
|
||||
*/
|
||||
export async function createPage(data: unknown): Promise<Page> {
|
||||
// Import Zod schema
|
||||
const { PageSchema, validateForCreate } = await import('./schemas');
|
||||
|
||||
// Validate input
|
||||
const validatedData = validateForCreate(PageSchema, data, 'Page');
|
||||
|
||||
const { rows } = await pool.query<Page>(
|
||||
`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<Page> {
|
||||
if (!isValidUUID(id)) {
|
||||
throw new Error('Invalid page ID format');
|
||||
}
|
||||
|
||||
const { PartialPageSchema, validateForUpdate } = await import('./schemas');
|
||||
|
||||
const validatedData = validateForUpdate(
|
||||
PartialPageSchema,
|
||||
{ ...(data as Record<string, any>), id },
|
||||
'Page'
|
||||
);
|
||||
|
||||
const [setClause, values] = buildUpdateSet(validatedData);
|
||||
values.push(id);
|
||||
|
||||
const { rows } = await pool.query<Page>(
|
||||
`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<boolean> {
|
||||
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<Page> {
|
||||
return updatePage(id, {
|
||||
status: 'published',
|
||||
published_at: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages count by status
|
||||
*/
|
||||
export async function getPagesCountByStatus(siteId?: string): Promise<Record<string, number>> {
|
||||
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<string, number>);
|
||||
}
|
||||
227
src/lib/shim/posts.ts
Normal file
227
src/lib/shim/posts.ts
Normal file
@@ -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<PaginationResult<Post>> {
|
||||
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<Post>(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<Post | null> {
|
||||
if (!isValidUUID(id)) {
|
||||
throw new Error('Invalid post ID format');
|
||||
}
|
||||
|
||||
const { rows } = await pool.query<Post>(
|
||||
'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<Post | null> {
|
||||
if (!isValidUUID(siteId)) {
|
||||
throw new Error('Invalid site ID format');
|
||||
}
|
||||
|
||||
const { rows } = await pool.query<Post>(
|
||||
'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<Post[]> {
|
||||
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<Post>(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<Post> {
|
||||
const { rows } = await pool.query<Post>(
|
||||
`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<Post>): Promise<Post> {
|
||||
if (!isValidUUID(id)) {
|
||||
throw new Error('Invalid post ID format');
|
||||
}
|
||||
|
||||
const [setClause, values] = buildUpdateSet(data);
|
||||
values.push(id);
|
||||
|
||||
const { rows } = await pool.query<Post>(
|
||||
`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<boolean> {
|
||||
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<Post> {
|
||||
return updatePost(id, {
|
||||
status: 'published',
|
||||
published_at: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get posts count by status
|
||||
*/
|
||||
export async function getPostsCountByStatus(siteId?: string): Promise<Record<string, number>> {
|
||||
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<string, number>);
|
||||
}
|
||||
@@ -99,6 +99,26 @@ export const CampaignSchema = z.object({
|
||||
|
||||
export type CampaignInput = z.infer<typeof CampaignSchema>;
|
||||
|
||||
/**
|
||||
* 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<typeof PageSchema>;
|
||||
|
||||
/**
|
||||
* GENERATION JOB SCHEMA
|
||||
*/
|
||||
@@ -122,6 +142,7 @@ export type GenerationJobInput = z.infer<typeof GenerationJobSchema>;
|
||||
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
|
||||
|
||||
92
src/pages/api/shim/pages/[id].ts
Normal file
92
src/pages/api/shim/pages/[id].ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
37
src/pages/api/shim/pages/create.ts
Normal file
37
src/pages/api/shim/pages/create.ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
44
src/pages/api/shim/pages/list.ts
Normal file
44
src/pages/api/shim/pages/list.ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
92
src/pages/api/shim/posts/[id].ts
Normal file
92
src/pages/api/shim/posts/[id].ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
37
src/pages/api/shim/posts/create.ts
Normal file
37
src/pages/api/shim/posts/create.ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
44
src/pages/api/shim/posts/list.ts
Normal file
44
src/pages/api/shim/posts/list.ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
35
src/pages/api/shim/sites/[id].ts
Normal file
35
src/pages/api/shim/sites/[id].ts
Normal file
@@ -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' }
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user