Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ coverage
!.env.example
.og-cache
.temp

# generated previews (binary, excluded from PR tooling)
templates/*/preview.png
55 changes: 39 additions & 16 deletions apps/server-node/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express, { type Request, type Response } from 'express';
import { nodeAdapter } from '@og-engine/adapter-node';
import { createHandler } from '@og-engine/core';
import type { OGRequest, MetaRequest } from '@og-engine/types';
import type { MetaRequest, OGRequest, PlatformSize } from '@og-engine/types';
import express, { type Request, type Response } from 'express';

const app = express();
const port = process.env.PORT ?? 3000;
Expand All @@ -12,23 +12,50 @@ const adapter = nodeAdapter({
});

const handler = createHandler({ platform: adapter });
const PLATFORM_SIZES: ReadonlyArray<PlatformSize> = [
'twitter-og',
'facebook-og',
'linkedin-og',
'ig-post',
'ig-story',
'discord',
'whatsapp',
'github',
'og'
];

function parseSize(value: unknown): PlatformSize {
if (typeof value === 'string' && PLATFORM_SIZES.includes(value as PlatformSize)) {
return value as PlatformSize;
}

return 'og';
}

function parseFormat(value: unknown): OGRequest['format'] {
if (value === 'jpeg' || value === 'png') {
return value;
}

return 'png';
}

app.get('/health', (_request: Request, response: Response) => {
response.json({ ok: true });
});

app.get('/api/og', async (req: Request, res: Response) => {
try {
const { template, size = 'og', format = 'png', ...params } = req.query;
const { template, size, format, ...params } = req.query;

if (!template) {
if (!template || typeof template !== 'string') {
return res.status(400).send('Missing template parameter');
}

const ogReq: OGRequest = {
template: template as string,
size: size as any,
format: format as any,
template,
size: parseSize(size),
format: parseFormat(format),
params: params as Record<string, string>
};

Expand All @@ -40,34 +67,30 @@ app.get('/api/og', async (req: Request, res: Response) => {
});
res.send(buffer);
} catch (error) {
console.error('Render error:', error);
res.status(500).send(error instanceof Error ? error.message : 'Internal Server Error');
}
});

app.get('/api/meta', async (req: Request, res: Response) => {
try {
const { template, size = 'og', ...params } = req.query;
const { template, size, ...params } = req.query;

if (!template) {
if (!template || typeof template !== 'string') {
return res.status(400).send('Missing template parameter');
}

const metaReq: MetaRequest = {
template: template as string,
size: size as any,
template,
size: parseSize(size),
params: params as Record<string, string>,
baseUrl: `${req.protocol}://${req.get('host')}`
};

const meta = await handler.handleMetaRequest(metaReq);
res.json(meta);
} catch (error) {
console.error('Meta error:', error);
res.status(500).send(error instanceof Error ? error.message : 'Internal Server Error');
}
});

app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
app.listen(port);
46 changes: 37 additions & 9 deletions apps/web/src/app/api/og/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
import { NextRequest } from 'next/server';
import { nodeAdapter } from '@og-engine/adapter-node';
import { createHandler } from '@og-engine/core';
import type { OGRequest } from '@og-engine/types';
import type { OGRequest, PlatformSize } from '@og-engine/types';


const PLATFORM_SIZES: ReadonlyArray<PlatformSize> = [
'twitter-og',
'facebook-og',
'linkedin-og',
'ig-post',
'ig-story',
'discord',
'whatsapp',
'github',
'og'
];

function parseSize(value: string | null): PlatformSize {
if (value && PLATFORM_SIZES.includes(value as PlatformSize)) {
return value as PlatformSize;
}

return 'og';
}

function parseFormat(value: string | null): OGRequest['format'] {
if (value === 'jpeg' || value === 'png') {
return value;
}

return 'png';
}

export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const template = searchParams.get('template');
const size = searchParams.get('size') || 'og';
const format = searchParams.get('format') || 'png';
const size = searchParams.get('size');
const format = searchParams.get('format');

if (!template) {
return new Response('Missing template', { status: 400 });
Expand All @@ -30,23 +59,22 @@ export async function GET(req: NextRequest) {

const ogReq: OGRequest = {
template,
size: size as any,
format: format as any,
size: parseSize(size),
format: parseFormat(format),
params
};

try {
const { buffer, contentType, headers } = await handler.handleImageRequest(ogReq);

return new Response(buffer as any, {
return new Response(buffer as unknown as BodyInit, {
status: 200,
headers: {
'Content-Type': contentType,
...headers
}
});
} catch (err) {
console.error('API Error details:', err);
return new Response(err instanceof Error ? err.message : 'Internal error', { status: 500 });
} catch (error) {
return new Response(error instanceof Error ? error.message : 'Internal error', { status: 500 });
}
}
46 changes: 23 additions & 23 deletions apps/web/src/app/api/templates/[id]/preview/route.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import { NextRequest, NextResponse } from 'next/server';
import fs from 'node:fs';
import path from 'node:path';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = params;
const previewPath = path.join(process.cwd(), '../../templates', id, 'preview.png');
const fallbackPath = path.join(process.cwd(), '../../templates', id, 'preview.svg');

try {
if (fs.existsSync(previewPath)) {
const buffer = fs.readFileSync(previewPath);
return new NextResponse(buffer, {
headers: { 'Content-Type': 'image/png' },
});
}
const { id } = await params;
const previewPath = path.join(process.cwd(), '../../templates', id, 'preview.png');
const fallbackPath = path.join(process.cwd(), '../../templates', id, 'preview.svg');

if (fs.existsSync(fallbackPath)) {
const buffer = fs.readFileSync(fallbackPath);
return new NextResponse(buffer, {
headers: { 'Content-Type': 'image/svg+xml' },
});
}
try {
if (fs.existsSync(previewPath)) {
const buffer = fs.readFileSync(previewPath);
return new NextResponse(buffer, {
headers: { 'Content-Type': 'image/png' }
});
}

return new NextResponse('Not Found', { status: 404 });
} catch (err) {
return new NextResponse('Internal Error', { status: 500 });
if (fs.existsSync(fallbackPath)) {
const buffer = fs.readFileSync(fallbackPath);
return new NextResponse(buffer, {
headers: { 'Content-Type': 'image/svg+xml' }
});
}

return new NextResponse('Not Found', { status: 404 });
} catch {
return new NextResponse('Internal Error', { status: 500 });
}
}
Loading
Loading