robodev 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/emit-starter.test.ts +2 -2
- package/templates/auth-chat/.rulesync/skills/media-feature/SKILL.md +6 -6
- package/templates/auth-chat/package.json +1 -1
- package/templates/backend/api/_lib.ts +40 -15
- package/templates/backend/api/planets/[id].ts +23 -8
- package/templates/backend/api/planets.ts +14 -9
- package/templates/backend/package.json +1 -1
- package/templates/empty/package.json +1 -1
- package/templates/space/.rulesync/skills/media-feature/SKILL.md +6 -6
- package/templates/space/api/_lib.ts +40 -15
- package/templates/space/api/planets/[id].ts +23 -8
- package/templates/space/api/planets.ts +14 -9
- package/templates/space/apps/fe/src/assets/locales/en/translation.json +1 -0
- package/templates/space/apps/fe/src/assets/locales/sl/translation.json +1 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetEditPage.tsx +69 -33
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx +23 -20
- package/templates/space/apps/fe/src/utils/planet-form.test.ts +68 -0
- package/templates/space/apps/fe/src/utils/planet-form.ts +49 -0
- package/templates/space/apps/fe/src/utils/planet-submit.ts +25 -0
- package/templates/space/openapi.json +14 -131
- package/templates/space/package.json +1 -1
- package/templates/backend/api/files/upload-request.ts +0 -48
- package/templates/backend/api/files/upload.ts +0 -29
- package/templates/space/api/files/upload-request.ts +0 -48
- package/templates/space/api/files/upload.ts +0 -29
- package/templates/space/apps/fe/src/utils/media-upload-headers.ts +0 -8
- package/templates/space/apps/fe/src/utils/media-upload.test.ts +0 -19
- package/templates/space/apps/fe/src/utils/media-upload.ts +0 -72
package/package.json
CHANGED
package/src/emit-starter.test.ts
CHANGED
|
@@ -84,8 +84,8 @@ test("collectFiles for space starter is Tiny APIs without apps/fe", async () =>
|
|
|
84
84
|
assert.ok(paths.has("api/planets.ts"));
|
|
85
85
|
assert.ok(paths.has("api/planets/paginate.ts"));
|
|
86
86
|
assert.ok(paths.has("api/aliens/labels.ts"));
|
|
87
|
-
assert.ok(paths.has("api/files/upload-request.ts"));
|
|
88
|
-
assert.ok(paths.has("api/files/upload.ts"));
|
|
87
|
+
assert.ok(!paths.has("api/files/upload-request.ts"));
|
|
88
|
+
assert.ok(!paths.has("api/files/upload.ts"));
|
|
89
89
|
assert.ok(!paths.has("index.html"));
|
|
90
90
|
assert.ok(!paths.has("src/main.tsx"));
|
|
91
91
|
assert.ok([...paths].every((path) => !path.startsWith("apps/")));
|
|
@@ -10,12 +10,12 @@ codexcli:
|
|
|
10
10
|
|
|
11
11
|
Preserve the upload contract:
|
|
12
12
|
|
|
13
|
-
1. Frontend selects a `File
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
16
|
-
4.
|
|
17
|
-
5. Feature reads resolve
|
|
13
|
+
1. Frontend selects a `File` and keeps it local until submit. Do not call `/api/files/*`.
|
|
14
|
+
2. Feature create/update is one `multipart/form-data` request (`POST` / `PUT`) with text fields and an optional `file`.
|
|
15
|
+
3. The handler validates text fields, optionally calls `savePlanetImage` (`ctx.storage.upload` public), and writes the feature row.
|
|
16
|
+
4. Update may send `clearImage=true` when the user cleared an existing image and picked nothing new. A new `file` wins over `clearImage`.
|
|
17
|
+
5. Feature reads resolve the stored media ID to `image: { id, url }`.
|
|
18
18
|
|
|
19
|
-
Never
|
|
19
|
+
Never use a standalone files API. File bytes stay on the device until submit.
|
|
20
20
|
|
|
21
21
|
Media metadata lives in the `media` table in `database.ts`. Bytes live in Robodev storage. Planet images use resource name `planet-image`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asc, eq, type InferSelectModel } from "@robodev-ai/sdk";
|
|
2
|
-
import type { ApiHandlerContext } from "@robodev-ai/sdk";
|
|
2
|
+
import type { ApiHandlerContext, UploadedFile } from "@robodev-ai/sdk";
|
|
3
3
|
import { aliens, media, planetLikes, planets } from "../database";
|
|
4
4
|
|
|
5
5
|
export const PLANET_IMAGE_RESOURCE = "planet-image";
|
|
@@ -339,21 +339,46 @@ export async function validateAlien(
|
|
|
339
339
|
return row ?? null;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
342
|
+
const ALLOWED_PLANET_IMAGE_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
343
|
+
const MAX_PLANET_IMAGE_BYTES = 2 * 1024 * 1024;
|
|
344
|
+
|
|
345
|
+
export async function savePlanetImage(
|
|
346
|
+
ctx: ApiHandlerContext<unknown, unknown>,
|
|
347
|
+
file: UploadedFile,
|
|
348
|
+
): Promise<{ id: string } | ReturnType<typeof fail>> {
|
|
349
|
+
const mimeType = file.mimetype || "application/octet-stream";
|
|
350
|
+
if (file.data.length > MAX_PLANET_IMAGE_BYTES) {
|
|
351
|
+
return fail(400, "File is too large");
|
|
352
|
+
}
|
|
353
|
+
if (!ALLOWED_PLANET_IMAGE_TYPES.has(mimeType)) {
|
|
354
|
+
return fail(400, "Unsupported image type");
|
|
353
355
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
|
|
357
|
+
const fileName = (file.filename || "image").replaceAll("/", "-");
|
|
358
|
+
const key = `planet-images/${ctx.user!.id}/${crypto.randomUUID()}-${fileName}`;
|
|
359
|
+
const [row] = await ctx.db
|
|
360
|
+
.insert(media)
|
|
361
|
+
.values({
|
|
362
|
+
key,
|
|
363
|
+
userId: ctx.user!.id,
|
|
364
|
+
resourceName: PLANET_IMAGE_RESOURCE,
|
|
365
|
+
fileName,
|
|
366
|
+
fileSize: file.data.length,
|
|
367
|
+
mimeType,
|
|
368
|
+
})
|
|
369
|
+
.returning();
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
await ctx.storage.upload(row!.key, file.data, {
|
|
373
|
+
public: true,
|
|
374
|
+
contentType: mimeType,
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return fail(503, error instanceof Error ? error.message : "Storage is not configured");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
await ctx.db.update(media).set({ uploaded: new Date() }).where(eq(media.id, row!.id));
|
|
381
|
+
return { id: row!.id };
|
|
357
382
|
}
|
|
358
383
|
|
|
359
384
|
export { aliens, media, planetLikes, planets };
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { defineApi, eq, z } from "@robodev-ai/sdk";
|
|
2
|
-
import { enrichOne, fail, findPlanet, planets,
|
|
2
|
+
import { enrichOne, fail, findPlanet, planets, savePlanetImage, validateAlien } from "../_lib";
|
|
3
|
+
|
|
4
|
+
const emptyToUndefined = (value: unknown) => (value === "" ? undefined : value);
|
|
3
5
|
|
|
4
6
|
const planetBody = z.object({
|
|
5
7
|
name: z.string().min(1),
|
|
6
|
-
alienId: z.string().
|
|
7
|
-
discoveryDate: z.string().
|
|
8
|
-
description: z.string().
|
|
9
|
-
|
|
8
|
+
alienId: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
9
|
+
discoveryDate: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
10
|
+
description: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
11
|
+
clearImage: z.preprocess((value) => {
|
|
12
|
+
if (value === "true" || value === true) return true;
|
|
13
|
+
if (value === "false" || value === false || value === "" || value === undefined) return undefined;
|
|
14
|
+
return value;
|
|
15
|
+
}, z.boolean().optional()),
|
|
10
16
|
});
|
|
11
17
|
|
|
12
18
|
export const get = defineApi({
|
|
@@ -20,6 +26,7 @@ export const get = defineApi({
|
|
|
20
26
|
|
|
21
27
|
export const put = defineApi({
|
|
22
28
|
auth: "required",
|
|
29
|
+
multipart: true,
|
|
23
30
|
body: planetBody,
|
|
24
31
|
handler: async (ctx) => {
|
|
25
32
|
const planet = await findPlanet(ctx.db, ctx.params.id);
|
|
@@ -30,8 +37,16 @@ export const put = defineApi({
|
|
|
30
37
|
if (alienId && !(await validateAlien(ctx.db, alienId))) {
|
|
31
38
|
return fail(404, "Alien not found");
|
|
32
39
|
}
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
|
|
41
|
+
const file = ctx.files?.find((f) => f.fieldname === "file") ?? ctx.files?.[0];
|
|
42
|
+
let imageId = planet.imageId ?? null;
|
|
43
|
+
if (file?.data.length) {
|
|
44
|
+
const saved = await savePlanetImage(ctx, file);
|
|
45
|
+
if (!("id" in saved)) return saved;
|
|
46
|
+
imageId = saved.id;
|
|
47
|
+
} else if (ctx.body.clearImage) {
|
|
48
|
+
imageId = null;
|
|
49
|
+
}
|
|
35
50
|
|
|
36
51
|
const [updated] = await ctx.db
|
|
37
52
|
.update(planets)
|
|
@@ -40,7 +55,7 @@ export const put = defineApi({
|
|
|
40
55
|
alienId,
|
|
41
56
|
discoveryDate: ctx.body.discoveryDate ? new Date(ctx.body.discoveryDate) : null,
|
|
42
57
|
description: ctx.body.description ?? null,
|
|
43
|
-
imageId
|
|
58
|
+
imageId,
|
|
44
59
|
updatedAt: new Date(),
|
|
45
60
|
})
|
|
46
61
|
.where(eq(planets.id, planet.id))
|
|
@@ -8,19 +8,18 @@ import {
|
|
|
8
8
|
loadLookups,
|
|
9
9
|
parseFilter,
|
|
10
10
|
planets,
|
|
11
|
+
savePlanetImage,
|
|
11
12
|
sortPlanets,
|
|
12
13
|
validateAlien,
|
|
13
|
-
validateImage,
|
|
14
14
|
} from "./_lib";
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const emptyToUndefined = (value: unknown) => (value === "" ? undefined : value);
|
|
17
17
|
|
|
18
18
|
const planetBody = z.object({
|
|
19
19
|
name: z.string().min(1),
|
|
20
|
-
alienId: z.string().
|
|
21
|
-
discoveryDate: z.string().
|
|
22
|
-
description: z.string().
|
|
23
|
-
image: imageRequest,
|
|
20
|
+
alienId: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
21
|
+
discoveryDate: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
22
|
+
description: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
export const get = defineApi({
|
|
@@ -42,14 +41,20 @@ export const get = defineApi({
|
|
|
42
41
|
|
|
43
42
|
export const post = defineApi({
|
|
44
43
|
auth: "required",
|
|
44
|
+
multipart: true,
|
|
45
45
|
body: planetBody,
|
|
46
46
|
handler: async (ctx) => {
|
|
47
47
|
const alienId = ctx.body.alienId || null;
|
|
48
48
|
if (alienId && !(await validateAlien(ctx.db, alienId))) {
|
|
49
49
|
return fail(404, "Alien not found");
|
|
50
50
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const file = ctx.files?.find((f) => f.fieldname === "file") ?? ctx.files?.[0];
|
|
52
|
+
let imageId: string | null = null;
|
|
53
|
+
if (file?.data.length) {
|
|
54
|
+
const saved = await savePlanetImage(ctx, file);
|
|
55
|
+
if (!("id" in saved)) return saved;
|
|
56
|
+
imageId = saved.id;
|
|
57
|
+
}
|
|
53
58
|
|
|
54
59
|
const [row] = await ctx.db
|
|
55
60
|
.insert(planets)
|
|
@@ -60,7 +65,7 @@ export const post = defineApi({
|
|
|
60
65
|
discoveryDate: ctx.body.discoveryDate ? new Date(ctx.body.discoveryDate) : null,
|
|
61
66
|
name: ctx.body.name,
|
|
62
67
|
description: ctx.body.description ?? null,
|
|
63
|
-
imageId
|
|
68
|
+
imageId,
|
|
64
69
|
})
|
|
65
70
|
.returning();
|
|
66
71
|
return enrichOne(ctx, row!, ctx.user!.id);
|
|
@@ -10,12 +10,12 @@ codexcli:
|
|
|
10
10
|
|
|
11
11
|
Preserve the upload contract:
|
|
12
12
|
|
|
13
|
-
1. Frontend selects a `File
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
16
|
-
4.
|
|
17
|
-
5. Feature reads resolve
|
|
13
|
+
1. Frontend selects a `File` and keeps it local until submit. Do not call `/api/files/*`.
|
|
14
|
+
2. Feature create/update is one `multipart/form-data` request (`POST` / `PUT`) with text fields and an optional `file`.
|
|
15
|
+
3. The handler validates text fields, optionally calls `savePlanetImage` (`ctx.storage.upload` public), and writes the feature row.
|
|
16
|
+
4. Update may send `clearImage=true` when the user cleared an existing image and picked nothing new. A new `file` wins over `clearImage`.
|
|
17
|
+
5. Feature reads resolve the stored media ID to `image: { id, url }`.
|
|
18
18
|
|
|
19
|
-
Never
|
|
19
|
+
Never use a standalone files API. File bytes stay on the device until submit.
|
|
20
20
|
|
|
21
21
|
Media metadata lives in the `media` table in `database.ts`. Bytes live in Robodev storage. Planet images use resource name `planet-image`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { asc, eq, type InferSelectModel } from "@robodev-ai/sdk";
|
|
2
|
-
import type { ApiHandlerContext } from "@robodev-ai/sdk";
|
|
2
|
+
import type { ApiHandlerContext, UploadedFile } from "@robodev-ai/sdk";
|
|
3
3
|
import { aliens, media, planetLikes, planets } from "../database";
|
|
4
4
|
|
|
5
5
|
export const PLANET_IMAGE_RESOURCE = "planet-image";
|
|
@@ -339,21 +339,46 @@ export async function validateAlien(
|
|
|
339
339
|
return row ?? null;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
342
|
+
const ALLOWED_PLANET_IMAGE_TYPES = new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
343
|
+
const MAX_PLANET_IMAGE_BYTES = 2 * 1024 * 1024;
|
|
344
|
+
|
|
345
|
+
export async function savePlanetImage(
|
|
346
|
+
ctx: ApiHandlerContext<unknown, unknown>,
|
|
347
|
+
file: UploadedFile,
|
|
348
|
+
): Promise<{ id: string } | ReturnType<typeof fail>> {
|
|
349
|
+
const mimeType = file.mimetype || "application/octet-stream";
|
|
350
|
+
if (file.data.length > MAX_PLANET_IMAGE_BYTES) {
|
|
351
|
+
return fail(400, "File is too large");
|
|
352
|
+
}
|
|
353
|
+
if (!ALLOWED_PLANET_IMAGE_TYPES.has(mimeType)) {
|
|
354
|
+
return fail(400, "Unsupported image type");
|
|
353
355
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
|
|
357
|
+
const fileName = (file.filename || "image").replaceAll("/", "-");
|
|
358
|
+
const key = `planet-images/${ctx.user!.id}/${crypto.randomUUID()}-${fileName}`;
|
|
359
|
+
const [row] = await ctx.db
|
|
360
|
+
.insert(media)
|
|
361
|
+
.values({
|
|
362
|
+
key,
|
|
363
|
+
userId: ctx.user!.id,
|
|
364
|
+
resourceName: PLANET_IMAGE_RESOURCE,
|
|
365
|
+
fileName,
|
|
366
|
+
fileSize: file.data.length,
|
|
367
|
+
mimeType,
|
|
368
|
+
})
|
|
369
|
+
.returning();
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
await ctx.storage.upload(row!.key, file.data, {
|
|
373
|
+
public: true,
|
|
374
|
+
contentType: mimeType,
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return fail(503, error instanceof Error ? error.message : "Storage is not configured");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
await ctx.db.update(media).set({ uploaded: new Date() }).where(eq(media.id, row!.id));
|
|
381
|
+
return { id: row!.id };
|
|
357
382
|
}
|
|
358
383
|
|
|
359
384
|
export { aliens, media, planetLikes, planets };
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { defineApi, eq, z } from "@robodev-ai/sdk";
|
|
2
|
-
import { enrichOne, fail, findPlanet, planets,
|
|
2
|
+
import { enrichOne, fail, findPlanet, planets, savePlanetImage, validateAlien } from "../_lib";
|
|
3
|
+
|
|
4
|
+
const emptyToUndefined = (value: unknown) => (value === "" ? undefined : value);
|
|
3
5
|
|
|
4
6
|
const planetBody = z.object({
|
|
5
7
|
name: z.string().min(1),
|
|
6
|
-
alienId: z.string().
|
|
7
|
-
discoveryDate: z.string().
|
|
8
|
-
description: z.string().
|
|
9
|
-
|
|
8
|
+
alienId: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
9
|
+
discoveryDate: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
10
|
+
description: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
11
|
+
clearImage: z.preprocess((value) => {
|
|
12
|
+
if (value === "true" || value === true) return true;
|
|
13
|
+
if (value === "false" || value === false || value === "" || value === undefined) return undefined;
|
|
14
|
+
return value;
|
|
15
|
+
}, z.boolean().optional()),
|
|
10
16
|
});
|
|
11
17
|
|
|
12
18
|
export const get = defineApi({
|
|
@@ -20,6 +26,7 @@ export const get = defineApi({
|
|
|
20
26
|
|
|
21
27
|
export const put = defineApi({
|
|
22
28
|
auth: "required",
|
|
29
|
+
multipart: true,
|
|
23
30
|
body: planetBody,
|
|
24
31
|
handler: async (ctx) => {
|
|
25
32
|
const planet = await findPlanet(ctx.db, ctx.params.id);
|
|
@@ -30,8 +37,16 @@ export const put = defineApi({
|
|
|
30
37
|
if (alienId && !(await validateAlien(ctx.db, alienId))) {
|
|
31
38
|
return fail(404, "Alien not found");
|
|
32
39
|
}
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
|
|
41
|
+
const file = ctx.files?.find((f) => f.fieldname === "file") ?? ctx.files?.[0];
|
|
42
|
+
let imageId = planet.imageId ?? null;
|
|
43
|
+
if (file?.data.length) {
|
|
44
|
+
const saved = await savePlanetImage(ctx, file);
|
|
45
|
+
if (!("id" in saved)) return saved;
|
|
46
|
+
imageId = saved.id;
|
|
47
|
+
} else if (ctx.body.clearImage) {
|
|
48
|
+
imageId = null;
|
|
49
|
+
}
|
|
35
50
|
|
|
36
51
|
const [updated] = await ctx.db
|
|
37
52
|
.update(planets)
|
|
@@ -40,7 +55,7 @@ export const put = defineApi({
|
|
|
40
55
|
alienId,
|
|
41
56
|
discoveryDate: ctx.body.discoveryDate ? new Date(ctx.body.discoveryDate) : null,
|
|
42
57
|
description: ctx.body.description ?? null,
|
|
43
|
-
imageId
|
|
58
|
+
imageId,
|
|
44
59
|
updatedAt: new Date(),
|
|
45
60
|
})
|
|
46
61
|
.where(eq(planets.id, planet.id))
|
|
@@ -8,19 +8,18 @@ import {
|
|
|
8
8
|
loadLookups,
|
|
9
9
|
parseFilter,
|
|
10
10
|
planets,
|
|
11
|
+
savePlanetImage,
|
|
11
12
|
sortPlanets,
|
|
12
13
|
validateAlien,
|
|
13
|
-
validateImage,
|
|
14
14
|
} from "./_lib";
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const emptyToUndefined = (value: unknown) => (value === "" ? undefined : value);
|
|
17
17
|
|
|
18
18
|
const planetBody = z.object({
|
|
19
19
|
name: z.string().min(1),
|
|
20
|
-
alienId: z.string().
|
|
21
|
-
discoveryDate: z.string().
|
|
22
|
-
description: z.string().
|
|
23
|
-
image: imageRequest,
|
|
20
|
+
alienId: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
21
|
+
discoveryDate: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
22
|
+
description: z.preprocess(emptyToUndefined, z.string().optional()),
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
export const get = defineApi({
|
|
@@ -42,14 +41,20 @@ export const get = defineApi({
|
|
|
42
41
|
|
|
43
42
|
export const post = defineApi({
|
|
44
43
|
auth: "required",
|
|
44
|
+
multipart: true,
|
|
45
45
|
body: planetBody,
|
|
46
46
|
handler: async (ctx) => {
|
|
47
47
|
const alienId = ctx.body.alienId || null;
|
|
48
48
|
if (alienId && !(await validateAlien(ctx.db, alienId))) {
|
|
49
49
|
return fail(404, "Alien not found");
|
|
50
50
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const file = ctx.files?.find((f) => f.fieldname === "file") ?? ctx.files?.[0];
|
|
52
|
+
let imageId: string | null = null;
|
|
53
|
+
if (file?.data.length) {
|
|
54
|
+
const saved = await savePlanetImage(ctx, file);
|
|
55
|
+
if (!("id" in saved)) return saved;
|
|
56
|
+
imageId = saved.id;
|
|
57
|
+
}
|
|
53
58
|
|
|
54
59
|
const [row] = await ctx.db
|
|
55
60
|
.insert(planets)
|
|
@@ -60,7 +65,7 @@ export const post = defineApi({
|
|
|
60
65
|
discoveryDate: ctx.body.discoveryDate ? new Date(ctx.body.discoveryDate) : null,
|
|
61
66
|
name: ctx.body.name,
|
|
62
67
|
description: ctx.body.description ?? null,
|
|
63
|
-
imageId
|
|
68
|
+
imageId,
|
|
64
69
|
})
|
|
65
70
|
.returning();
|
|
66
71
|
return enrichOne(ctx, row!, ctx.user!.id);
|