Skip to content

Commit bebc8ae

Browse files
authored
feat: Add projects and templates API (#61)
1 parent 2be927c commit bebc8ae

File tree

4 files changed

+243
-5
lines changed

4 files changed

+243
-5
lines changed

apps/api/auth/src/main.ts

Lines changed: 198 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { Hono } from 'hono';
2-
import { D1Database } from '@cloudflare/workers-types';
2+
import { D1Database, R2Bucket } from '@cloudflare/workers-types';
33
import { typeid } from 'typeid-js';
44

55
interface ResponseWrapper<T, E> {
66
data: T;
77
error: E;
88
}
99

10-
const response = <T, E> (data: T, error ?: E): ResponseWrapper<T, E> => {
10+
const response = <T, E>(data: T, error?: E): ResponseWrapper<T, E> => {
1111
return {
1212
data,
1313
error
@@ -16,6 +16,7 @@ const response = <T, E> (data: T, error ?: E): ResponseWrapper<T, E> => {
1616

1717
type Bindings = {
1818
DB: D1Database;
19+
BUCKET: R2Bucket;
1920
}
2021

2122
const app = new Hono<{ Bindings: Bindings }>();
@@ -84,7 +85,7 @@ interface CreateOAuthParams {
8485
oaid: string;
8586
provider: string;
8687
email: string;
87-
}
88+
};
8889

8990
type CreateUserRequest = CreateUserParams & CreateOAuthParams;
9091

@@ -155,4 +156,198 @@ app.post('/providers', async (c) => {
155156
return c.json(response(result));
156157
});
157158

159+
interface Template {
160+
id: string;
161+
userId: string;
162+
name: string;
163+
createdAt: number;
164+
updatedAt: number;
165+
deletedAt: number;
166+
};
167+
168+
type CreateTemplateParams = Pick<Template,
169+
| 'name'
170+
| 'userId'
171+
>;
172+
173+
type FindTemplateParams = Pick<Template,
174+
| 'userId'
175+
>;
176+
177+
interface TemplateDataParams {
178+
data: JSON;
179+
};
180+
181+
type UpdateTemplateParams = Pick<Template,
182+
| 'name'
183+
>;
184+
185+
type CreateTemplateRequest = CreateTemplateParams & TemplateDataParams;
186+
type UpdateTemplateRequest = UpdateTemplateParams & TemplateDataParams;
187+
188+
app.post('/templates', async (c) => {
189+
const body = await c.req.json<CreateTemplateRequest>();
190+
191+
const id = typeid('tmpl').toString();
192+
193+
const key = ['templates', body.userId, id].join('/');
194+
195+
const createTemplateStatement = c.env.DB.prepare(`
196+
INSERT INTO template (id, user_id, name, created_at)
197+
VALUES (?1, ?2, ?3, ?4)
198+
`);
199+
200+
try {
201+
await c.env.BUCKET.put(key, JSON.stringify(body.data));
202+
203+
const { results } = await createTemplateStatement.bind(id, body.userId, body.name, Date.now()).run();
204+
205+
return c.json(response(results));
206+
} catch (e) {
207+
return c.json(response(null, e));
208+
}
209+
});
210+
211+
app.get('/templates/:userId', async (c) => {
212+
const { userId } = c.req.param() as FindTemplateParams;
213+
214+
const getTemplateStatement = c.env.DB.prepare(`
215+
SELECT
216+
t.id,
217+
t.user_id 'userId',
218+
t.key,
219+
t.created_at 'createdAt',
220+
t.updated_at 'updatedAt'
221+
FROM template as t
222+
WHERE t.user_id = ?1 AND t.deleted_at NOT NULL
223+
`);
224+
225+
const { results } = await getTemplateStatement.bind(userId).all<Template>();
226+
227+
results.map((template) => {
228+
template.id;
229+
});
230+
231+
return c.json(response(results));
232+
});
233+
234+
app.get('/templates/:userId/:id', async (c) => {
235+
const { userId, id } = c.req.param();
236+
const key = ['templates', userId, id].join('/');
237+
238+
try {
239+
const objectRef = await c.env.BUCKET.get(key);
240+
const data = objectRef.json();
241+
242+
return c.json(response(data));
243+
} catch (e) {
244+
return c.json(response(null, e));
245+
}
246+
});
247+
248+
app.put('/templates/:userId/:id', async (c) => {
249+
const { userId, id } = c.req.param();
250+
const { data, name } = await c.req.json() as UpdateTemplateRequest;
251+
252+
const key = ['templates', userId, id].join('/');
253+
254+
const updateTemplateStatement = c.env.DB.prepare(`
255+
UPDATE template
256+
SET name = ?1
257+
WHERE id = ?2 AND deleted_at NOT NULL
258+
`);
259+
260+
try {
261+
await c.env.BUCKET.put(key, JSON.stringify(data));
262+
263+
const { results } = await updateTemplateStatement.bind(name, id).run();
264+
265+
return c.json(response(results));
266+
} catch (e) {
267+
return c.json(response(null, e));
268+
}
269+
});
270+
271+
interface Project {
272+
id: string;
273+
userId: string;
274+
templateId: string;
275+
name: string;
276+
createdAt: number;
277+
updatedAt: number;
278+
deletedAt: number;
279+
};
280+
281+
type CreateProjectRequest = Pick<Project,
282+
| 'userId'
283+
| 'templateId'
284+
| 'name'
285+
>;
286+
287+
type FindProjectRequest = Pick<Project,
288+
| 'userId'
289+
>;
290+
291+
type UpdateProjectRequest = Pick<Project,
292+
| 'id'
293+
| 'name'
294+
>;
295+
296+
app.post('/projects', async (c) => {
297+
const body = await c.req.json<CreateProjectRequest>();
298+
299+
const createProjectStatement = c.env.DB.prepare(`
300+
INSERT INTO project (id, user_id, template_id, name, created_at)
301+
VALUES (?1, ?2, ?3, ?4, ?5)
302+
`);
303+
304+
const id = typeid('proj').toString();
305+
306+
const result = createProjectStatement.bind(id, body.userId, body.templateId, body.name, Date.now()).run();
307+
308+
return c.json(response(result));
309+
});
310+
311+
app.get('/projects', async (c) => {
312+
const { userId } = c.req.query() as FindProjectRequest;
313+
314+
const getProjectStatement = c.env.DB.prepare(`
315+
SELECT
316+
p.id,
317+
p.user_id 'userId',
318+
p.template_id 'templateId',
319+
p.created_at 'createdAt',
320+
p.updated_at 'updatedAt'
321+
FROM project as p
322+
WHERE p.user_id = ?1 AND p.deleted_at NOT NULL
323+
`);
324+
325+
const { results } = await getProjectStatement.bind(userId).all<Project>();
326+
327+
return c.json(response(results));
328+
});
329+
330+
app.patch('/projects', async (c) => {
331+
const body = await c.req.json<UpdateProjectRequest>();
332+
333+
const updateProjectStatement = c.env.DB.prepare(`
334+
UPDATE project
335+
SET name = ?1
336+
WHERE id = ?2 AND deleted_at NOT NULL
337+
`);
338+
339+
const { results } = await updateProjectStatement.bind(body.name, body.id).run();
340+
341+
return c.json(response(results));
342+
});
343+
344+
// interface Generate {
345+
// id: string;
346+
// projectId: string;
347+
// key: string;
348+
// args: string;
349+
// createdAt: number;
350+
// deletedAt: number;
351+
// }
352+
158353
export default app;

db/schema/schema.sql

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,37 @@ CREATE TABLE IF NOT EXISTS oauth_account (
3939
FOREIGN KEY (user_id) REFERENCES user (id),
4040
FOREIGN KEY (oauth_provider_id) REFERENCES oauth_provider (id)
4141
);
42+
43+
CREATE TABLE IF NOT EXISTS template (
44+
id TEXT PRIMARY KEY,
45+
user_id TEXT NOT NULL,
46+
name TEXT NOT NULL,
47+
created_at INTEGER NOT NULL,
48+
updated_at INTEGER,
49+
deleted_at INTEGER,
50+
51+
FOREIGN KEY (user_id) REFERENCES user (id)
52+
);
53+
54+
CREATE TABLE IF NOT EXISTS project (
55+
id TEXT PRIMARY KEY,
56+
user_id TEXT NOT NULL,
57+
template_id TEXT NOT NULL,
58+
name TEXT NOT NULL,
59+
created_at INTEGER NOT NULL,
60+
updated_at INTEGER,
61+
deleted_at INTEGER,
62+
63+
FOREIGN KEY (user_id) REFERENCES user (id),
64+
FOREIGN KEY (template_id) REFERENCES template (id)
65+
);
66+
67+
CREATE TABLE IF NOT EXISTS generate (
68+
id TEXT PRIMARY KEY,
69+
project_id TEXT NOT NULL,
70+
args TEXT,
71+
created_at INTEGER NOT NULL,
72+
deleted_at INTEGER,
73+
74+
FOREIGN KEY (project_id) REFERENCES project (id)
75+
);

libs/web/dashboard/editor/src/lib/toolbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Frame } from './context';
55
export default component$(() => {
66
const { frame } = useContext(Frame);
77

8-
const exportObject = $(() => {
8+
const saveTemplate = $(() => {
99
if (!frame) return;
1010

1111
console.log(frame.toObject());
@@ -15,7 +15,7 @@ export default component$(() => {
1515
<Navigation.Bar>
1616
<Box></Box>
1717
<Box>
18-
<Navigation.Action onClick$={exportObject}>Export</Navigation.Action>
18+
<Navigation.Action onClick$={saveTemplate}>Save</Navigation.Action>
1919
</Box>
2020
</Navigation.Bar>
2121
);

wrangler.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ workers_dev = true
1010
d1_databases = [
1111
{ binding = "DB", database_name = "dev-certifine", database_id = "ba778eb8-bf82-4984-a21a-88dc8913f2fe" }
1212
]
13+
r2_buckets = [
14+
{ binding = "BUCKET", bucket_name = "dev-certifine" }
15+
]
1316

1417
[env.staging]
1518
vars = { ENVIRONMENT = "staging" }
@@ -20,6 +23,9 @@ workers_dev = true
2023
d1_databases = [
2124
{ binding = "DB", database_name = "staging-certifine", database_id = "c0534c42-4517-4068-af84-a458e02866b0" }
2225
]
26+
r2_buckets = [
27+
{ binding = "BUCKET", bucket_name = "staging-certifine" }
28+
]
2329

2430
[env.production]
2531
vars = { ENVIRONMENT = "production" }
@@ -31,3 +37,6 @@ workers_dev = false
3137
d1_databases = [
3238
{ binding = "DB", database_name = "certifine", database_id = "fae83b0b-b945-4a5b-ad5f-23d3b13dd581" }
3339
]
40+
r2_buckets = [
41+
{ binding = "BUCKET", bucket_name = "certifine" }
42+
]

0 commit comments

Comments
 (0)