@podosoft/podokit 0.1.1 → 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 +17 -3
- 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 +2 -2
|
@@ -1,17 +1,81 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
2
5
|
|
|
3
|
-
/* Semantic tokens. shadcn-svelte components read these CSS variables. */
|
|
4
6
|
:root {
|
|
7
|
+
--radius: 0.625rem;
|
|
5
8
|
--background: oklch(1 0 0);
|
|
6
|
-
--foreground: oklch(0.
|
|
9
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
10
|
+
--card: oklch(1 0 0);
|
|
11
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
12
|
+
--popover: oklch(1 0 0);
|
|
13
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
14
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
15
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
16
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
17
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
18
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
19
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
20
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
21
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
22
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
23
|
+
--border: oklch(0.92 0.004 286.32);
|
|
24
|
+
--input: oklch(0.92 0.004 286.32);
|
|
25
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.dark {
|
|
29
|
+
--background: oklch(0.141 0.005 285.823);
|
|
30
|
+
--foreground: oklch(0.985 0 0);
|
|
31
|
+
--card: oklch(0.21 0.006 285.885);
|
|
32
|
+
--card-foreground: oklch(0.985 0 0);
|
|
33
|
+
--popover: oklch(0.21 0.006 285.885);
|
|
34
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
35
|
+
--primary: oklch(0.92 0.004 286.32);
|
|
36
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
37
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
38
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
39
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
40
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
41
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
42
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
43
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
44
|
+
--border: oklch(1 0 0 / 10%);
|
|
45
|
+
--input: oklch(1 0 0 / 15%);
|
|
46
|
+
--ring: oklch(0.552 0.016 285.938);
|
|
7
47
|
}
|
|
8
48
|
|
|
9
|
-
|
|
10
|
-
--
|
|
11
|
-
--
|
|
49
|
+
@theme inline {
|
|
50
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
51
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
52
|
+
--radius-lg: var(--radius);
|
|
53
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
54
|
+
--color-background: var(--background);
|
|
55
|
+
--color-foreground: var(--foreground);
|
|
56
|
+
--color-card: var(--card);
|
|
57
|
+
--color-card-foreground: var(--card-foreground);
|
|
58
|
+
--color-popover: var(--popover);
|
|
59
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
60
|
+
--color-primary: var(--primary);
|
|
61
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
62
|
+
--color-secondary: var(--secondary);
|
|
63
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
64
|
+
--color-muted: var(--muted);
|
|
65
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
66
|
+
--color-accent: var(--accent);
|
|
67
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
68
|
+
--color-destructive: var(--destructive);
|
|
69
|
+
--color-border: var(--border);
|
|
70
|
+
--color-input: var(--input);
|
|
71
|
+
--color-ring: var(--ring);
|
|
12
72
|
}
|
|
13
73
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
74
|
+
@layer base {
|
|
75
|
+
* {
|
|
76
|
+
@apply border-border outline-ring/50;
|
|
77
|
+
}
|
|
78
|
+
body {
|
|
79
|
+
@apply bg-background text-foreground;
|
|
80
|
+
}
|
|
17
81
|
}
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/button/button.svelte
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
3
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
|
|
4
|
+
import { type VariantProps, tv } from "tailwind-variants";
|
|
5
|
+
|
|
6
|
+
export const buttonVariants = tv({
|
|
7
|
+
base: "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 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:not-aria-[haspopup]:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
11
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
12
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
13
|
+
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
14
|
+
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
|
|
15
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
19
|
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
20
|
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
21
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
22
|
+
icon: "size-8",
|
|
23
|
+
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
24
|
+
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
|
25
|
+
"icon-lg": "size-9",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: {
|
|
29
|
+
variant: "default",
|
|
30
|
+
size: "default",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
|
35
|
+
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
|
|
36
|
+
|
|
37
|
+
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
|
38
|
+
WithElementRef<HTMLAnchorAttributes> & {
|
|
39
|
+
variant?: ButtonVariant;
|
|
40
|
+
size?: ButtonSize;
|
|
41
|
+
};
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<script lang="ts">
|
|
45
|
+
let {
|
|
46
|
+
class: className,
|
|
47
|
+
variant = "default",
|
|
48
|
+
size = "default",
|
|
49
|
+
ref = $bindable(null),
|
|
50
|
+
href = undefined,
|
|
51
|
+
type = "button",
|
|
52
|
+
disabled,
|
|
53
|
+
children,
|
|
54
|
+
...restProps
|
|
55
|
+
}: ButtonProps = $props();
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
{#if href}
|
|
59
|
+
<a
|
|
60
|
+
bind:this={ref}
|
|
61
|
+
data-slot="button"
|
|
62
|
+
class={cn(buttonVariants({ variant, size }), className)}
|
|
63
|
+
href={disabled ? undefined : href}
|
|
64
|
+
aria-disabled={disabled}
|
|
65
|
+
role={disabled ? "link" : undefined}
|
|
66
|
+
tabindex={disabled ? -1 : undefined}
|
|
67
|
+
{...restProps}
|
|
68
|
+
>
|
|
69
|
+
{@render children?.()}
|
|
70
|
+
</a>
|
|
71
|
+
{:else}
|
|
72
|
+
<button
|
|
73
|
+
bind:this={ref}
|
|
74
|
+
data-slot="button"
|
|
75
|
+
class={cn(buttonVariants({ variant, size }), className)}
|
|
76
|
+
{type}
|
|
77
|
+
{disabled}
|
|
78
|
+
{...restProps}
|
|
79
|
+
>
|
|
80
|
+
{@render children?.()}
|
|
81
|
+
</button>
|
|
82
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Root, {
|
|
2
|
+
type ButtonProps,
|
|
3
|
+
type ButtonSize,
|
|
4
|
+
type ButtonVariant,
|
|
5
|
+
buttonVariants,
|
|
6
|
+
} from "./button.svelte";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
Root,
|
|
10
|
+
type ButtonProps as Props,
|
|
11
|
+
//
|
|
12
|
+
Root as Button,
|
|
13
|
+
buttonVariants,
|
|
14
|
+
type ButtonProps,
|
|
15
|
+
type ButtonSize,
|
|
16
|
+
type ButtonVariant,
|
|
17
|
+
};
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-action.svelte
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="card-action"
|
|
16
|
+
class={cn(
|
|
17
|
+
"cn-card-action col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-content.svelte
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="card-content"
|
|
16
|
+
class={cn("px-4 group-data-[size=sm]/card:px-3", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<p
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="card-description"
|
|
16
|
+
class={cn("text-muted-foreground text-sm", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</p>
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-footer.svelte
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="card-footer"
|
|
16
|
+
class={cn("bg-muted/50 rounded-b-xl border-t p-4 group-data-[size=sm]/card:p-3 flex items-center", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</div>
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-header.svelte
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="card-header"
|
|
16
|
+
class={cn(
|
|
17
|
+
"gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...restProps}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/card/card-title.svelte
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
bind:this={ref}
|
|
15
|
+
data-slot="card-title"
|
|
16
|
+
class={cn("text-base leading-snug font-medium group-data-[size=sm]/card:text-sm", className)}
|
|
17
|
+
{...restProps}
|
|
18
|
+
>
|
|
19
|
+
{@render children?.()}
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import { cn, type WithElementRef } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
children,
|
|
9
|
+
size = "default",
|
|
10
|
+
...restProps
|
|
11
|
+
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: "default" | "sm" } = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
bind:this={ref}
|
|
16
|
+
data-slot="card"
|
|
17
|
+
data-size={size}
|
|
18
|
+
class={cn("ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)}
|
|
19
|
+
{...restProps}
|
|
20
|
+
>
|
|
21
|
+
{@render children?.()}
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Root from "./card.svelte";
|
|
2
|
+
import Content from "./card-content.svelte";
|
|
3
|
+
import Description from "./card-description.svelte";
|
|
4
|
+
import Footer from "./card-footer.svelte";
|
|
5
|
+
import Header from "./card-header.svelte";
|
|
6
|
+
import Title from "./card-title.svelte";
|
|
7
|
+
import Action from "./card-action.svelte";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
Root,
|
|
11
|
+
Content,
|
|
12
|
+
Description,
|
|
13
|
+
Footer,
|
|
14
|
+
Header,
|
|
15
|
+
Title,
|
|
16
|
+
Action,
|
|
17
|
+
//
|
|
18
|
+
Root as Card,
|
|
19
|
+
Content as CardContent,
|
|
20
|
+
Description as CardDescription,
|
|
21
|
+
Footer as CardFooter,
|
|
22
|
+
Header as CardHeader,
|
|
23
|
+
Title as CardTitle,
|
|
24
|
+
Action as CardAction,
|
|
25
|
+
};
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/checkbox/checkbox.svelte
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
|
3
|
+
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
|
4
|
+
import CheckIcon from '@lucide/svelte/icons/check';
|
|
5
|
+
import MinusIcon from '@lucide/svelte/icons/minus';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
ref = $bindable(null),
|
|
9
|
+
checked = $bindable(false),
|
|
10
|
+
indeterminate = $bindable(false),
|
|
11
|
+
class: className,
|
|
12
|
+
...restProps
|
|
13
|
+
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<CheckboxPrimitive.Root
|
|
17
|
+
bind:ref
|
|
18
|
+
data-slot="checkbox"
|
|
19
|
+
class={cn(
|
|
20
|
+
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
bind:checked
|
|
24
|
+
bind:indeterminate
|
|
25
|
+
{...restProps}
|
|
26
|
+
>
|
|
27
|
+
{#snippet children({ checked, indeterminate })}
|
|
28
|
+
<div
|
|
29
|
+
data-slot="checkbox-indicator"
|
|
30
|
+
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
|
31
|
+
>
|
|
32
|
+
{#if checked}
|
|
33
|
+
<CheckIcon />
|
|
34
|
+
{:else if indeterminate}
|
|
35
|
+
<MinusIcon />
|
|
36
|
+
{/if}
|
|
37
|
+
</div>
|
|
38
|
+
{/snippet}
|
|
39
|
+
</CheckboxPrimitive.Root>
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/input/input.svelte
ADDED
|
@@ -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}
|
package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/label/label.svelte
ADDED
|
@@ -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,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 };
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { Button } from "$lib/components/ui/button";
|
|
3
|
+
import * as Card from "$lib/components/ui/card";
|
|
4
|
+
|
|
2
5
|
type Health = { status: string } | { error: string };
|
|
3
6
|
|
|
4
7
|
let health = $state<Health | null>(null);
|
|
@@ -10,17 +13,21 @@
|
|
|
10
13
|
</script>
|
|
11
14
|
|
|
12
15
|
<main class="mx-auto flex min-h-full max-w-2xl flex-col gap-6 p-8">
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class="w-fit rounded-md border border-current/20 px-4 py-2 text-sm font-medium hover:opacity-80"
|
|
18
|
-
onclick={check}
|
|
19
|
-
>
|
|
20
|
-
Check API health
|
|
21
|
-
</button>
|
|
16
|
+
<div>
|
|
17
|
+
<h1 class="text-3xl font-bold">{{projectName}}</h1>
|
|
18
|
+
<p class="text-muted-foreground text-sm">Full-stack starter generated with PodoKit.</p>
|
|
19
|
+
</div>
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
21
|
+
<Card.Root>
|
|
22
|
+
<Card.Header>
|
|
23
|
+
<Card.Title>API health</Card.Title>
|
|
24
|
+
<Card.Description>Check the NestJS API through the SvelteKit server proxy.</Card.Description>
|
|
25
|
+
</Card.Header>
|
|
26
|
+
<Card.Content class="flex flex-col gap-4">
|
|
27
|
+
<Button class="w-fit" onclick={check}>Check API health</Button>
|
|
28
|
+
{#if health}
|
|
29
|
+
<pre class="bg-muted rounded-md p-4 text-sm">{JSON.stringify(health, null, 2)}</pre>
|
|
30
|
+
{/if}
|
|
31
|
+
</Card.Content>
|
|
32
|
+
</Card.Root>
|
|
26
33
|
</main>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Body, Controller, Get, HttpCode, Post, Req, UseGuards } from "@nestjs/common";
|
|
2
|
+
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
|
3
|
+
import type { Request } from "express";
|
|
4
|
+
import { AuthService } from "./auth.service";
|
|
5
|
+
import { RegisterDto } from "./dto/register.dto";
|
|
6
|
+
import { LoginDto } from "./dto/login.dto";
|
|
7
|
+
import { JwtAuthGuard } from "./jwt-auth.guard";
|
|
8
|
+
|
|
9
|
+
@ApiTags("auth")
|
|
10
|
+
@Controller("auth")
|
|
11
|
+
export class AuthController {
|
|
12
|
+
constructor(private readonly auth: AuthService) {}
|
|
13
|
+
|
|
14
|
+
@Post("register")
|
|
15
|
+
register(@Body() dto: RegisterDto) {
|
|
16
|
+
return this.auth.register(dto);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Post("login")
|
|
20
|
+
@HttpCode(200)
|
|
21
|
+
login(@Body() dto: LoginDto) {
|
|
22
|
+
return this.auth.login(dto);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@UseGuards(JwtAuthGuard)
|
|
26
|
+
@ApiBearerAuth()
|
|
27
|
+
@Get("me")
|
|
28
|
+
me(@Req() req: Request): unknown {
|
|
29
|
+
return req.user;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { JwtModule } from "@nestjs/jwt";
|
|
3
|
+
import { PassportModule } from "@nestjs/passport";
|
|
4
|
+
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
5
|
+
import { AuthController } from "./auth.controller";
|
|
6
|
+
import { AuthService } from "./auth.service";
|
|
7
|
+
import { JwtStrategy } from "./jwt.strategy";
|
|
8
|
+
import { User } from "./user.entity";
|
|
9
|
+
|
|
10
|
+
@Module({
|
|
11
|
+
imports: [
|
|
12
|
+
TypeOrmModule.forFeature([User]),
|
|
13
|
+
PassportModule,
|
|
14
|
+
JwtModule.register({
|
|
15
|
+
secret: process.env.JWT_SECRET ?? "change-me-in-production",
|
|
16
|
+
signOptions: { expiresIn: process.env.JWT_EXPIRES_IN ?? "3600s" },
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
controllers: [AuthController],
|
|
20
|
+
providers: [AuthService, JwtStrategy],
|
|
21
|
+
})
|
|
22
|
+
export class AuthModule {}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ConflictException, Injectable, UnauthorizedException } from "@nestjs/common";
|
|
2
|
+
import { JwtService } from "@nestjs/jwt";
|
|
3
|
+
import { InjectRepository } from "@nestjs/typeorm";
|
|
4
|
+
import { Repository } from "typeorm";
|
|
5
|
+
import * as bcrypt from "bcryptjs";
|
|
6
|
+
import { User } from "./user.entity";
|
|
7
|
+
import { RegisterDto } from "./dto/register.dto";
|
|
8
|
+
import { LoginDto } from "./dto/login.dto";
|
|
9
|
+
|
|
10
|
+
export interface AuthResult {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
user: { id: string; email: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Injectable()
|
|
16
|
+
export class AuthService {
|
|
17
|
+
constructor(
|
|
18
|
+
@InjectRepository(User) private readonly users: Repository<User>,
|
|
19
|
+
private readonly jwt: JwtService,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
async register(dto: RegisterDto): Promise<AuthResult> {
|
|
23
|
+
const existing = await this.users.findOne({ where: { email: dto.email } });
|
|
24
|
+
if (existing) {
|
|
25
|
+
throw new ConflictException("Email already registered");
|
|
26
|
+
}
|
|
27
|
+
const passwordHash = await bcrypt.hash(dto.password, 10);
|
|
28
|
+
const user = await this.users.save(this.users.create({ email: dto.email, passwordHash }));
|
|
29
|
+
return this.sign(user);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async login(dto: LoginDto): Promise<AuthResult> {
|
|
33
|
+
const user = await this.users.findOne({ where: { email: dto.email } });
|
|
34
|
+
if (!user || !(await bcrypt.compare(dto.password, user.passwordHash))) {
|
|
35
|
+
throw new UnauthorizedException("Invalid credentials");
|
|
36
|
+
}
|
|
37
|
+
return this.sign(user);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private sign(user: User): AuthResult {
|
|
41
|
+
const accessToken = this.jwt.sign({ sub: user.id, email: user.email });
|
|
42
|
+
return { accessToken, user: { id: user.id, email: user.email } };
|
|
43
|
+
}
|
|
44
|
+
}
|