File size: 4,435 Bytes
cfea436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { Hono } from 'hono';
import { eq, desc, inArray } from 'drizzle-orm';
import { prompts, images } from '../db/schema';
import type { Env, Variables } from '../index';

export const promptsApi = new Hono<{ Bindings: Env; Variables: Variables }>();

const uid = () => crypto.randomUUID();
const now = () => Date.now();

// List
promptsApi.get('/', async (c) => {
  const db = c.var.db;
  const rows = await db.select().from(prompts).orderBy(desc(prompts.updatedAt));
  // Attach images per prompt
  const ids = rows.map((r) => r.id);
  const imgs = ids.length
    ? await db.select().from(images).where(inArray(images.promptId, ids))
    : [];
  const grouped: Record<string, typeof imgs> = {};
  for (const im of imgs) {
    (grouped[im.promptId] ||= []).push(im);
  }
  return c.json(
    rows.map((r) => ({
      ...r,
      tags: safeJson<string[]>(r.tags || '[]', []),
      images: (grouped[r.id] || []).sort((a, b) => a.createdAt - b.createdAt),
    }))
  );
});

// Get one
promptsApi.get('/:id', async (c) => {
  const db = c.var.db;
  const id = c.req.param('id');
  const row = (await db.select().from(prompts).where(eq(prompts.id, id))).at(0);
  if (!row) return c.json({ error: 'not_found' }, 404);
  const imgs = await db.select().from(images).where(eq(images.promptId, id));
  return c.json({
    ...row,
    tags: safeJson<string[]>(row.tags || '[]', []),
    images: imgs.sort((a, b) => a.createdAt - b.createdAt),
  });
});

// Create
promptsApi.post('/', async (c) => {
  const db = c.var.db;
  const body = await c.req.json<{
    title: string;
    contentImage?: string;
    contentVideo?: string;
    tags?: string[];
    sourceUrl?: string;
  }>();
  if (!body.title?.trim()) return c.json({ error: 'title_required' }, 400);
  const ci = (body.contentImage || '').trim();
  const cv = (body.contentVideo || '').trim();
  if (!ci && !cv) return c.json({ error: 'prompt_required' }, 400);
  const id = uid();
  const t = now();
  await db.insert(prompts).values({
    id,
    title: body.title.trim(),
    contentImage: ci,
    contentVideo: cv,
    tags: JSON.stringify(body.tags || []),
    sourceUrl: body.sourceUrl?.trim() || null,
    isFavorite: false,
    createdAt: t,
    updatedAt: t,
  });
  return c.json({ id });
});

// Update
promptsApi.put('/:id', async (c) => {
  const db = c.var.db;
  const id = c.req.param('id');
  const body = await c.req.json<{
    title?: string;
    contentImage?: string;
    contentVideo?: string;
    tags?: string[];
    sourceUrl?: string;
  }>();
  const exists = (await db.select().from(prompts).where(eq(prompts.id, id))).at(0);
  if (!exists) return c.json({ error: 'not_found' }, 404);
  const ci = body.contentImage !== undefined ? body.contentImage.trim() : exists.contentImage || '';
  const cv = body.contentVideo !== undefined ? body.contentVideo.trim() : exists.contentVideo || '';
  if (!ci && !cv) return c.json({ error: 'prompt_required' }, 400);
  const promptChanged =
    ci !== (exists.contentImage || '') || cv !== (exists.contentVideo || '');
  await db
    .update(prompts)
    .set({
      title: body.title?.trim() || exists.title,
      contentImage: ci,
      contentVideo: cv,
      tags: body.tags !== undefined ? JSON.stringify(body.tags) : exists.tags,
      sourceUrl: body.sourceUrl !== undefined ? body.sourceUrl.trim() || null : exists.sourceUrl,
      // 提示词变了,清掉旧译文
      translationImage: promptChanged ? null : exists.translationImage,
      translationVideo: promptChanged ? null : exists.translationVideo,
      updatedAt: now(),
    })
    .where(eq(prompts.id, id));
  return c.json({ ok: true });
});

// Delete
promptsApi.delete('/:id', async (c) => {
  const db = c.var.db;
  const id = c.req.param('id');
  await db.delete(images).where(eq(images.promptId, id));
  await db.delete(prompts).where(eq(prompts.id, id));
  return c.json({ ok: true });
});

// Toggle favorite
promptsApi.post('/:id/favorite', async (c) => {
  const db = c.var.db;
  const id = c.req.param('id');
  const row = (await db.select().from(prompts).where(eq(prompts.id, id))).at(0);
  if (!row) return c.json({ error: 'not_found' }, 404);
  await db
    .update(prompts)
    .set({ isFavorite: !row.isFavorite, updatedAt: now() })
    .where(eq(prompts.id, id));
  return c.json({ isFavorite: !row.isFavorite });
});

function safeJson<T>(s: string, fb: T): T {
  try { return JSON.parse(s) as T; } catch { return fb; }
}