@podosoft/podokit 0.1.0 → 0.2.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/README.md +98 -0
- package/dist/add.d.ts +40 -0
- package/dist/add.js +114 -0
- package/dist/create.d.ts +2 -1
- package/dist/create.js +3 -2
- package/dist/index.js +39 -1
- package/dist/prompt.d.ts +0 -1
- package/dist/prompt.js +6 -7
- package/dist/templates/fullstack-nest-svelte/README.md +20 -8
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +14 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +11 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +20 -15
- package/dist/templates/fullstack-nest-svelte/apps/api/src/database/data-source.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/api/src/health/health.controller.ts +15 -5
- package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +8 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/components.json +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +9 -2
- package/dist/templates/fullstack-nest-svelte/apps/web/src/app.css +72 -8
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/button.svelte +82 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/index.ts +17 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card.svelte +22 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/index.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/input.svelte +48 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/label.svelte +20 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/utils.ts +11 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +19 -12
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.controller.ts +31 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.module.ts +22 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/auth.service.ts +44 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/login.dto.ts +12 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/dto/register.dto.ts +13 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt-auth.guard.ts +5 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/jwt.strategy.ts +23 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/auth/user.entity.ts +16 -0
- package/dist/templates/modules/auth-jwt/files/apps/api/src/migrations/1720200000000-InitUsers.ts +23 -0
- package/dist/templates/modules/auth-jwt/module.manifest.json +31 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/demo.processor.ts +12 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/dto/create-job.dto.ts +9 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.controller.ts +29 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/jobs.module.ts +15 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/queue.ts +8 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +20 -0
- package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +14 -0
- package/dist/templates/modules/bullmq/files/infra/docker/worker.compose.example.yml +18 -0
- package/dist/templates/modules/bullmq/files/infra/k3s/worker-deployment.yaml +22 -0
- package/dist/templates/modules/bullmq/module.manifest.json +28 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.controller.ts +29 -0
- package/dist/templates/modules/file-upload/files/apps/api/src/files/files.module.ts +9 -0
- package/dist/templates/modules/file-upload/module.manifest.json +19 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/dto/start-job.dto.ts +11 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/job-progress.module.ts +13 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.bridge.ts +19 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.controller.ts +17 -0
- package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.processor.ts +25 -0
- package/dist/templates/modules/job-progress/module.manifest.json +23 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/dto/put-object.dto.ts +8 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.config.ts +31 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.controller.ts +29 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.module.ts +11 -0
- package/dist/templates/modules/object-storage-s3/files/apps/api/src/storage/storage.service.ts +37 -0
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +33 -0
- package/dist/templates/modules/object-storage-s3/module.manifest.json +31 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/cache.controller.ts +21 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/dto/set-cache.dto.ts +14 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.module.ts +12 -0
- package/dist/templates/modules/redis/files/apps/api/src/redis/redis.service.ts +43 -0
- package/dist/templates/modules/redis/module.manifest.json +21 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/dto/publish-event.dto.ts +8 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.controller.ts +25 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.module.ts +12 -0
- package/dist/templates/modules/sse/files/apps/api/src/events/events.service.ts +17 -0
- package/dist/templates/modules/sse/module.manifest.json +16 -0
- package/dist/templates/todo/README.md +40 -0
- package/dist/templates/todo/apps/api/Dockerfile +22 -0
- package/dist/templates/todo/apps/api/nest-cli.json +5 -0
- package/dist/templates/todo/apps/api/package.json +44 -0
- package/dist/templates/todo/apps/api/src/app.module.ts +19 -0
- package/dist/templates/todo/apps/api/src/common/all-exceptions.filter.ts +43 -0
- package/dist/templates/todo/apps/api/src/common/app-exception.ts +12 -0
- package/dist/templates/todo/apps/api/src/config/env.validation.ts +23 -0
- package/dist/templates/todo/apps/api/src/database/data-source.ts +19 -0
- package/dist/templates/todo/apps/api/src/health/health.controller.ts +23 -0
- package/dist/templates/todo/apps/api/src/health/health.module.ts +7 -0
- package/dist/templates/todo/apps/api/src/main.ts +29 -0
- package/dist/templates/todo/apps/api/src/migrations/1720100000000-InitTodos.ts +22 -0
- package/dist/templates/todo/apps/api/src/todos/dto/create-todo.dto.ts +10 -0
- package/dist/templates/todo/apps/api/src/todos/dto/update-todo.dto.ts +10 -0
- package/dist/templates/todo/apps/api/src/todos/todo.entity.ts +16 -0
- package/dist/templates/todo/apps/api/src/todos/todos.controller.ts +38 -0
- package/dist/templates/todo/apps/api/src/todos/todos.module.ts +12 -0
- package/dist/templates/todo/apps/api/src/todos/todos.service.ts +41 -0
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +23 -0
- package/dist/templates/todo/apps/api/tsconfig.json +21 -0
- package/dist/templates/todo/apps/web/Dockerfile +22 -0
- package/dist/templates/todo/apps/web/components.json +15 -0
- package/dist/templates/todo/apps/web/package.json +35 -0
- package/dist/templates/todo/apps/web/src/app.css +81 -0
- package/dist/templates/todo/apps/web/src/app.d.ts +11 -0
- package/dist/templates/todo/apps/web/src/app.html +11 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/button/button.svelte +82 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/button/index.ts +17 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-action.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-content.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/card.svelte +22 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/card/index.ts +25 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/checkbox.svelte +39 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/checkbox/index.ts +6 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/input/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/input/input.svelte +48 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/label/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/label/label.svelte +20 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/README.md +7 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/en.ts +10 -0
- package/dist/templates/todo/apps/web/src/lib/i18n/ko.ts +8 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +16 -0
- package/dist/templates/todo/apps/web/src/lib/utils.ts +11 -0
- package/dist/templates/todo/apps/web/src/routes/+layout.svelte +9 -0
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +95 -0
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +24 -0
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +24 -0
- package/dist/templates/todo/apps/web/static/.gitkeep +0 -0
- package/dist/templates/todo/apps/web/svelte.config.js +15 -0
- package/dist/templates/todo/apps/web/tsconfig.json +9 -0
- package/dist/templates/todo/apps/web/vite.config.ts +7 -0
- package/dist/templates/todo/dot-env.example +16 -0
- package/dist/templates/todo/dot-gitignore +9 -0
- package/dist/templates/todo/infra/docker/docker-compose.yml +29 -0
- package/dist/templates/todo/infra/k3s/api-deployment.yaml +24 -0
- package/dist/templates/todo/infra/k3s/configmap.yaml +10 -0
- package/dist/templates/todo/infra/k3s/ingress.yaml +18 -0
- package/dist/templates/todo/infra/k3s/namespace.yaml +4 -0
- package/dist/templates/todo/infra/k3s/secret.example.yaml +10 -0
- package/dist/templates/todo/infra/k3s/services.yaml +21 -0
- package/dist/templates/todo/infra/k3s/web-deployment.yaml +20 -0
- package/dist/templates/todo/package.json +13 -0
- package/dist/templates.d.ts +10 -0
- package/dist/templates.js +33 -0
- package/package.json +14 -4
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
|
6
|
+
|
|
7
|
+
type Props = WithElementRef<
|
|
8
|
+
Omit<HTMLInputAttributes, "type"> &
|
|
9
|
+
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
ref = $bindable(null),
|
|
14
|
+
value = $bindable(),
|
|
15
|
+
type,
|
|
16
|
+
files = $bindable(),
|
|
17
|
+
class: className,
|
|
18
|
+
"data-slot": dataSlot = "input",
|
|
19
|
+
...restProps
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if type === "file"}
|
|
24
|
+
<input
|
|
25
|
+
bind:this={ref}
|
|
26
|
+
data-slot={dataSlot}
|
|
27
|
+
class={cn(
|
|
28
|
+
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
type="file"
|
|
32
|
+
bind:files
|
|
33
|
+
bind:value
|
|
34
|
+
{...restProps}
|
|
35
|
+
/>
|
|
36
|
+
{:else}
|
|
37
|
+
<input
|
|
38
|
+
bind:this={ref}
|
|
39
|
+
data-slot={dataSlot}
|
|
40
|
+
class={cn(
|
|
41
|
+
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-lg border bg-transparent px-2.5 py-1 text-base transition-colors file:h-6 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{type}
|
|
45
|
+
bind:value
|
|
46
|
+
{...restProps}
|
|
47
|
+
/>
|
|
48
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Label as LabelPrimitive } from "bits-ui";
|
|
3
|
+
import { cn } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: LabelPrimitive.RootProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<LabelPrimitive.Root
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="label"
|
|
15
|
+
class={cn(
|
|
16
|
+
"gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
/>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# i18n
|
|
2
|
+
|
|
3
|
+
Locales use [typesafe-i18n](https://github.com/ivanhofer/typesafe-i18n).
|
|
4
|
+
|
|
5
|
+
- `en.ts` is the base locale; `ko.ts` mirrors its shape.
|
|
6
|
+
- Run `npx typesafe-i18n` to generate the typed runtime and Svelte store,
|
|
7
|
+
then use `$LL.appTitle()` in components. Add keys to every locale.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BaseTranslation } from "typesafe-i18n";
|
|
2
|
+
|
|
3
|
+
// Base locale. Run `npx typesafe-i18n` to generate the typed runtime,
|
|
4
|
+
// then access strings as `$LL.appTitle()` in components.
|
|
5
|
+
const en = {
|
|
6
|
+
appTitle: "{{projectName}}",
|
|
7
|
+
checkHealth: "Check API health",
|
|
8
|
+
} satisfies BaseTranslation;
|
|
9
|
+
|
|
10
|
+
export default en;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Server-side proxy boundary. The browser never talks to the API directly:
|
|
2
|
+
// only these allowlisted headers are forwarded to BACKEND_INTERNAL_URL.
|
|
3
|
+
const FORWARDED_HEADERS = ["authorization", "cookie", "content-type"];
|
|
4
|
+
|
|
5
|
+
export function backendBaseUrl(): string {
|
|
6
|
+
return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function backendProxyHeaders(request: Request): Headers {
|
|
10
|
+
const headers = new Headers();
|
|
11
|
+
for (const name of FORWARDED_HEADERS) {
|
|
12
|
+
const value = request.headers.get(name);
|
|
13
|
+
if (value) headers.set(name, value);
|
|
14
|
+
}
|
|
15
|
+
return headers;
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type WithoutChild<T> = T extends { child?: unknown } ? Omit<T, "child"> : T;
|
|
9
|
+
export type WithoutChildren<T> = T extends { children?: unknown } ? Omit<T, "children"> : T;
|
|
10
|
+
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
|
11
|
+
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from "$lib/components/ui/button";
|
|
3
|
+
import { Input } from "$lib/components/ui/input";
|
|
4
|
+
import { Checkbox } from "$lib/components/ui/checkbox";
|
|
5
|
+
import * as Card from "$lib/components/ui/card";
|
|
6
|
+
|
|
7
|
+
type Todo = { id: string; title: string; completed: boolean };
|
|
8
|
+
|
|
9
|
+
let todos = $state<Todo[]>([]);
|
|
10
|
+
let title = $state("");
|
|
11
|
+
let error = $state<string | null>(null);
|
|
12
|
+
|
|
13
|
+
async function load(): Promise<void> {
|
|
14
|
+
const res = await fetch("/api/todos");
|
|
15
|
+
if (res.ok) todos = await res.json();
|
|
16
|
+
else error = `Failed to load (HTTP ${res.status})`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function add(event: SubmitEvent): Promise<void> {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
const value = title.trim();
|
|
22
|
+
if (!value) return;
|
|
23
|
+
const res = await fetch("/api/todos", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "content-type": "application/json" },
|
|
26
|
+
body: JSON.stringify({ title: value }),
|
|
27
|
+
});
|
|
28
|
+
if (res.ok) {
|
|
29
|
+
title = "";
|
|
30
|
+
await load();
|
|
31
|
+
} else {
|
|
32
|
+
error = `Failed to add (HTTP ${res.status})`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function toggle(todo: Todo): Promise<void> {
|
|
37
|
+
await fetch(`/api/todos/${todo.id}`, {
|
|
38
|
+
method: "PATCH",
|
|
39
|
+
headers: { "content-type": "application/json" },
|
|
40
|
+
body: JSON.stringify({ completed: !todo.completed }),
|
|
41
|
+
});
|
|
42
|
+
await load();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function remove(todo: Todo): Promise<void> {
|
|
46
|
+
await fetch(`/api/todos/${todo.id}`, { method: "DELETE" });
|
|
47
|
+
await load();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$effect(() => {
|
|
51
|
+
void load();
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<main class="mx-auto flex min-h-full max-w-xl flex-col gap-6 p-8">
|
|
56
|
+
<div>
|
|
57
|
+
<h1 class="text-3xl font-bold">{{projectName}}</h1>
|
|
58
|
+
<p class="text-muted-foreground text-sm">Full-stack starter generated with PodoKit.</p>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<Card.Root>
|
|
62
|
+
<Card.Header>
|
|
63
|
+
<Card.Title>Todos</Card.Title>
|
|
64
|
+
<Card.Description>A NestJS + SvelteKit CRUD example.</Card.Description>
|
|
65
|
+
</Card.Header>
|
|
66
|
+
<Card.Content class="flex flex-col gap-4">
|
|
67
|
+
<form class="flex gap-2" onsubmit={add}>
|
|
68
|
+
<Input placeholder="Add a todo…" bind:value={title} />
|
|
69
|
+
<Button type="submit">Add</Button>
|
|
70
|
+
</form>
|
|
71
|
+
|
|
72
|
+
{#if error}
|
|
73
|
+
<p class="text-destructive text-sm">{error}</p>
|
|
74
|
+
{/if}
|
|
75
|
+
|
|
76
|
+
<ul class="divide-border flex flex-col divide-y">
|
|
77
|
+
{#each todos as todo (todo.id)}
|
|
78
|
+
<li class="flex items-center gap-3 py-3">
|
|
79
|
+
<Checkbox checked={todo.completed} onCheckedChange={() => toggle(todo)} />
|
|
80
|
+
<span
|
|
81
|
+
class="flex-1 text-sm"
|
|
82
|
+
class:line-through={todo.completed}
|
|
83
|
+
class:text-muted-foreground={todo.completed}
|
|
84
|
+
>
|
|
85
|
+
{todo.title}
|
|
86
|
+
</span>
|
|
87
|
+
<Button variant="ghost" size="sm" onclick={() => remove(todo)}>Delete</Button>
|
|
88
|
+
</li>
|
|
89
|
+
{:else}
|
|
90
|
+
<li class="text-muted-foreground py-6 text-center text-sm">No todos yet — add one above.</li>
|
|
91
|
+
{/each}
|
|
92
|
+
</ul>
|
|
93
|
+
</Card.Content>
|
|
94
|
+
</Card.Root>
|
|
95
|
+
</main>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
export const GET: RequestHandler = async ({ request }) => {
|
|
5
|
+
const upstream = await fetch(`${backendBaseUrl()}/health`, {
|
|
6
|
+
headers: backendProxyHeaders(request),
|
|
7
|
+
});
|
|
8
|
+
return new Response(await upstream.text(), {
|
|
9
|
+
status: upstream.status,
|
|
10
|
+
headers: { "content-type": "application/json" },
|
|
11
|
+
});
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
export const GET: RequestHandler = async ({ request }) => {
|
|
5
|
+
const upstream = await fetch(`${backendBaseUrl()}/todos`, { headers: backendProxyHeaders(request) });
|
|
6
|
+
return new Response(await upstream.text(), {
|
|
7
|
+
status: upstream.status,
|
|
8
|
+
headers: { "content-type": "application/json" },
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
13
|
+
const headers = backendProxyHeaders(request);
|
|
14
|
+
headers.set("content-type", "application/json");
|
|
15
|
+
const upstream = await fetch(`${backendBaseUrl()}/todos`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers,
|
|
18
|
+
body: await request.text(),
|
|
19
|
+
});
|
|
20
|
+
return new Response(await upstream.text(), {
|
|
21
|
+
status: upstream.status,
|
|
22
|
+
headers: { "content-type": "application/json" },
|
|
23
|
+
});
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, backendProxyHeaders } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
export const PATCH: RequestHandler = async ({ request, params }) => {
|
|
5
|
+
const headers = backendProxyHeaders(request);
|
|
6
|
+
headers.set("content-type", "application/json");
|
|
7
|
+
const upstream = await fetch(`${backendBaseUrl()}/todos/${params.id}`, {
|
|
8
|
+
method: "PATCH",
|
|
9
|
+
headers,
|
|
10
|
+
body: await request.text(),
|
|
11
|
+
});
|
|
12
|
+
return new Response(await upstream.text(), {
|
|
13
|
+
status: upstream.status,
|
|
14
|
+
headers: { "content-type": "application/json" },
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const DELETE: RequestHandler = async ({ request, params }) => {
|
|
19
|
+
const upstream = await fetch(`${backendBaseUrl()}/todos/${params.id}`, {
|
|
20
|
+
method: "DELETE",
|
|
21
|
+
headers: backendProxyHeaders(request),
|
|
22
|
+
});
|
|
23
|
+
return new Response(null, { status: upstream.status });
|
|
24
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import adapter from "@sveltejs/adapter-node";
|
|
2
|
+
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
|
|
4
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
5
|
+
const config = {
|
|
6
|
+
preprocess: vitePreprocess(),
|
|
7
|
+
kit: {
|
|
8
|
+
adapter: adapter(),
|
|
9
|
+
alias: {
|
|
10
|
+
$i18n: "src/lib/i18n",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default config;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copy to .env and adjust. Never commit real secrets.
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
PORT=3000
|
|
4
|
+
|
|
5
|
+
POSTGRES_HOST=localhost
|
|
6
|
+
POSTGRES_PORT=5432
|
|
7
|
+
POSTGRES_USER=podokit
|
|
8
|
+
POSTGRES_PASSWORD=podokit
|
|
9
|
+
POSTGRES_DB=podokit
|
|
10
|
+
|
|
11
|
+
REDIS_HOST=localhost
|
|
12
|
+
REDIS_PORT=6379
|
|
13
|
+
|
|
14
|
+
# Web -> API boundary (server-side only; not exposed to the browser)
|
|
15
|
+
BACKEND_INTERNAL_URL=http://localhost:3000
|
|
16
|
+
CORS_ORIGIN=http://localhost:5173
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_USER: ${POSTGRES_USER:-podokit}
|
|
6
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-podokit}
|
|
7
|
+
POSTGRES_DB: ${POSTGRES_DB:-podokit}
|
|
8
|
+
ports:
|
|
9
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
10
|
+
volumes:
|
|
11
|
+
- pgdata:/var/lib/postgresql/data
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-podokit}"]
|
|
14
|
+
interval: 5s
|
|
15
|
+
timeout: 5s
|
|
16
|
+
retries: 5
|
|
17
|
+
|
|
18
|
+
redis:
|
|
19
|
+
image: redis:7-alpine
|
|
20
|
+
ports:
|
|
21
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
22
|
+
healthcheck:
|
|
23
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
24
|
+
interval: 5s
|
|
25
|
+
timeout: 5s
|
|
26
|
+
retries: 5
|
|
27
|
+
|
|
28
|
+
volumes:
|
|
29
|
+
pgdata:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: api
|
|
5
|
+
namespace: podokit
|
|
6
|
+
spec:
|
|
7
|
+
replicas: 1
|
|
8
|
+
selector:
|
|
9
|
+
matchLabels: { app: api }
|
|
10
|
+
template:
|
|
11
|
+
metadata:
|
|
12
|
+
labels: { app: api }
|
|
13
|
+
spec:
|
|
14
|
+
containers:
|
|
15
|
+
- name: api
|
|
16
|
+
image: ghcr.io/example/podokit-api:latest
|
|
17
|
+
ports:
|
|
18
|
+
- containerPort: 3000
|
|
19
|
+
envFrom:
|
|
20
|
+
- configMapRef: { name: app-config }
|
|
21
|
+
- secretRef: { name: app-secrets }
|
|
22
|
+
readinessProbe:
|
|
23
|
+
httpGet: { path: /health, port: 3000 }
|
|
24
|
+
initialDelaySeconds: 5
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
apiVersion: networking.k8s.io/v1
|
|
2
|
+
kind: Ingress
|
|
3
|
+
metadata:
|
|
4
|
+
name: app
|
|
5
|
+
namespace: podokit
|
|
6
|
+
spec:
|
|
7
|
+
rules:
|
|
8
|
+
- host: example.com
|
|
9
|
+
http:
|
|
10
|
+
paths:
|
|
11
|
+
- path: /api
|
|
12
|
+
pathType: Prefix
|
|
13
|
+
backend:
|
|
14
|
+
service: { name: api, port: { number: 3000 } }
|
|
15
|
+
- path: /
|
|
16
|
+
pathType: Prefix
|
|
17
|
+
backend:
|
|
18
|
+
service: { name: web, port: { number: 3000 } }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Example only. Never commit real secrets. Create the real Secret out-of-band:
|
|
2
|
+
# kubectl -n podokit create secret generic app-secrets --from-literal=POSTGRES_PASSWORD=...
|
|
3
|
+
apiVersion: v1
|
|
4
|
+
kind: Secret
|
|
5
|
+
metadata:
|
|
6
|
+
name: app-secrets
|
|
7
|
+
namespace: podokit
|
|
8
|
+
type: Opaque
|
|
9
|
+
stringData:
|
|
10
|
+
POSTGRES_PASSWORD: "change-me"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: Service
|
|
3
|
+
metadata:
|
|
4
|
+
name: api
|
|
5
|
+
namespace: podokit
|
|
6
|
+
spec:
|
|
7
|
+
selector: { app: api }
|
|
8
|
+
ports:
|
|
9
|
+
- port: 3000
|
|
10
|
+
targetPort: 3000
|
|
11
|
+
---
|
|
12
|
+
apiVersion: v1
|
|
13
|
+
kind: Service
|
|
14
|
+
metadata:
|
|
15
|
+
name: web
|
|
16
|
+
namespace: podokit
|
|
17
|
+
spec:
|
|
18
|
+
selector: { app: web }
|
|
19
|
+
ports:
|
|
20
|
+
- port: 3000
|
|
21
|
+
targetPort: 3000
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: web
|
|
5
|
+
namespace: podokit
|
|
6
|
+
spec:
|
|
7
|
+
replicas: 1
|
|
8
|
+
selector:
|
|
9
|
+
matchLabels: { app: web }
|
|
10
|
+
template:
|
|
11
|
+
metadata:
|
|
12
|
+
labels: { app: web }
|
|
13
|
+
spec:
|
|
14
|
+
containers:
|
|
15
|
+
- name: web
|
|
16
|
+
image: ghcr.io/example/podokit-web:latest
|
|
17
|
+
ports:
|
|
18
|
+
- containerPort: 3000
|
|
19
|
+
envFrom:
|
|
20
|
+
- configMapRef: { name: app-config }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"engines": { "node": ">=20" },
|
|
6
|
+
"workspaces": ["apps/*"],
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "npm run dev --workspaces --if-present",
|
|
9
|
+
"build": "npm run build --workspaces --if-present",
|
|
10
|
+
"lint": "npm run lint --workspaces --if-present",
|
|
11
|
+
"test": "npm run test --workspaces --if-present"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface TemplateInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const TEMPLATES: TemplateInfo[];
|
|
6
|
+
export declare const DEFAULT_TEMPLATE: string;
|
|
7
|
+
export declare const TEMPLATE_NAMES: string[];
|
|
8
|
+
export declare function isKnownTemplate(name: string): boolean;
|
|
9
|
+
/** Human-readable list of templates with descriptions, for prompts and help. */
|
|
10
|
+
export declare function templateListText(): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TEMPLATE_NAMES = exports.DEFAULT_TEMPLATE = exports.TEMPLATES = void 0;
|
|
4
|
+
exports.isKnownTemplate = isKnownTemplate;
|
|
5
|
+
exports.templateListText = templateListText;
|
|
6
|
+
// Order matters: the first entry is the default and prompts list them in order.
|
|
7
|
+
exports.TEMPLATES = [
|
|
8
|
+
{
|
|
9
|
+
name: "fullstack-nest-svelte",
|
|
10
|
+
description: "NestJS + SvelteKit starter — TypeORM wired, Swagger, no domain code (clean)",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "todo",
|
|
14
|
+
description: "Fullstack starter plus a Todo CRUD example (DB entity, migration, UI)",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "base",
|
|
18
|
+
description: "Minimal npm workspace to build up from scratch",
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
exports.DEFAULT_TEMPLATE = exports.TEMPLATES[0].name;
|
|
22
|
+
exports.TEMPLATE_NAMES = exports.TEMPLATES.map((t) => t.name);
|
|
23
|
+
function isKnownTemplate(name) {
|
|
24
|
+
return exports.TEMPLATE_NAMES.includes(name);
|
|
25
|
+
}
|
|
26
|
+
/** Human-readable list of templates with descriptions, for prompts and help. */
|
|
27
|
+
function templateListText() {
|
|
28
|
+
const width = Math.max(...exports.TEMPLATES.map((t) => t.name.length));
|
|
29
|
+
return exports.TEMPLATES.map((t) => {
|
|
30
|
+
const suffix = t.name === exports.DEFAULT_TEMPLATE ? " (default)" : "";
|
|
31
|
+
return ` ${t.name.padEnd(width)} ${t.description}${suffix}`;
|
|
32
|
+
}).join("\n");
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@podosoft/podokit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An opinionated but extensible starter toolkit and CLI for full-stack TypeScript apps with NestJS, SvelteKit, TailwindCSS, shadcn-svelte, Docker, and k3s.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,11 +8,21 @@
|
|
|
8
8
|
"url": "git+https://github.com/podosoft-dev/podokit.git",
|
|
9
9
|
"directory": "packages/cli"
|
|
10
10
|
},
|
|
11
|
-
"keywords": [
|
|
11
|
+
"keywords": [
|
|
12
|
+
"nestjs",
|
|
13
|
+
"sveltekit",
|
|
14
|
+
"starter",
|
|
15
|
+
"cli",
|
|
16
|
+
"scaffold",
|
|
17
|
+
"tailwindcss",
|
|
18
|
+
"shadcn"
|
|
19
|
+
],
|
|
12
20
|
"bin": {
|
|
13
21
|
"podo": "dist/index.js"
|
|
14
22
|
},
|
|
15
|
-
"files": [
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
16
26
|
"engines": {
|
|
17
27
|
"node": ">=20"
|
|
18
28
|
},
|
|
@@ -26,6 +36,6 @@
|
|
|
26
36
|
"test": "vitest run"
|
|
27
37
|
},
|
|
28
38
|
"dependencies": {
|
|
29
|
-
"@podosoft/podokit-template-engine": "^0.
|
|
39
|
+
"@podosoft/podokit-template-engine": "^0.2.0"
|
|
30
40
|
}
|
|
31
41
|
}
|