mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Updating schema, adding admin pages, adding page for adding roles to admin on viewing user, block only admin to reach admin page.
This commit is contained in:
parent
c1aaad7f6c
commit
5e174c875f
31 changed files with 4148 additions and 335 deletions
13
drizzle/0004_glossy_enchantress.sql
Normal file
13
drizzle/0004_glossy_enchantress.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
ALTER TABLE "categories" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "collection_items" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "collections" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "expansions" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "external_ids" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "games" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "mechanics" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "publishers" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "roles" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "user_roles" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "wishlist_items" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();--> statement-breakpoint
|
||||||
|
ALTER TABLE "wishlists" ALTER COLUMN "id" SET DEFAULT gen_random_uuid();
|
||||||
2
drizzle/0005_light_captain_marvel.sql
Normal file
2
drizzle/0005_light_captain_marvel.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE "roles" ALTER COLUMN "cuid" SET NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "roles" ALTER COLUMN "name" SET NOT NULL;
|
||||||
1513
drizzle/meta/0004_snapshot.json
Normal file
1513
drizzle/meta/0004_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
1513
drizzle/meta/0005_snapshot.json
Normal file
1513
drizzle/meta/0005_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -29,6 +29,20 @@
|
||||||
"when": 1710268371021,
|
"when": 1710268371021,
|
||||||
"tag": "0003_mushy_madame_masque",
|
"tag": "0003_mushy_madame_masque",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 4,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1710277583673,
|
||||||
|
"tag": "0004_glossy_enchantress",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 5,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1710366724519,
|
||||||
|
"tag": "0005_light_captain_marvel",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
22
package.json
22
package.json
|
|
@ -26,11 +26,11 @@
|
||||||
"@playwright/test": "^1.42.1",
|
"@playwright/test": "^1.42.1",
|
||||||
"@resvg/resvg-js": "^2.6.0",
|
"@resvg/resvg-js": "^2.6.0",
|
||||||
"@sveltejs/adapter-auto": "^3.1.1",
|
"@sveltejs/adapter-auto": "^3.1.1",
|
||||||
"@sveltejs/enhanced-img": "^0.1.8",
|
"@sveltejs/enhanced-img": "^0.1.9",
|
||||||
"@sveltejs/kit": "^2.5.3",
|
"@sveltejs/kit": "^2.5.4",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/node": "^20.11.26",
|
"@types/node": "^20.11.27",
|
||||||
"@types/pg": "^8.11.2",
|
"@types/pg": "^8.11.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
|
|
@ -45,20 +45,21 @@
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
"postcss-import": "^16.0.1",
|
"postcss-import": "^16.0.1",
|
||||||
"postcss-load-config": "^5.0.3",
|
"postcss-load-config": "^5.0.3",
|
||||||
"postcss-preset-env": "^9.5.0",
|
"postcss-preset-env": "^9.5.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-svelte": "^3.2.2",
|
"prettier-plugin-svelte": "^3.2.2",
|
||||||
"sass": "^1.71.1",
|
"sass": "^1.72.0",
|
||||||
"satori": "^0.10.13",
|
"satori": "^0.10.13",
|
||||||
"satori-html": "^0.3.2",
|
"satori-html": "^0.3.2",
|
||||||
"svelte": "^4.2.12",
|
"svelte": "^4.2.12",
|
||||||
"svelte-check": "^3.6.6",
|
"svelte-check": "^3.6.7",
|
||||||
|
"svelte-headless-table": "^0.18.2",
|
||||||
"svelte-meta-tags": "^3.1.1",
|
"svelte-meta-tags": "^3.1.1",
|
||||||
"svelte-preprocess": "^5.1.3",
|
"svelte-preprocess": "^5.1.3",
|
||||||
"svelte-sequential-preprocessor": "^2.0.1",
|
"svelte-sequential-preprocessor": "^2.0.1",
|
||||||
"sveltekit-flash-message": "^2.4.4",
|
"sveltekit-flash-message": "^2.4.4",
|
||||||
"sveltekit-rate-limiter": "^0.4.3",
|
"sveltekit-rate-limiter": "^0.4.3",
|
||||||
"sveltekit-superforms": "^2.8.1",
|
"sveltekit-superforms": "^2.9.0",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tslib": "^2.6.1",
|
"tslib": "^2.6.1",
|
||||||
|
|
@ -86,20 +87,21 @@
|
||||||
"@sveltejs/adapter-vercel": "^5.1.1",
|
"@sveltejs/adapter-vercel": "^5.1.1",
|
||||||
"@types/feather-icons": "^4.29.4",
|
"@types/feather-icons": "^4.29.4",
|
||||||
"@vercel/og": "^0.5.20",
|
"@vercel/og": "^0.5.20",
|
||||||
"bits-ui": "^0.19.6",
|
"bits-ui": "^0.19.7",
|
||||||
"boardgamegeekclient": "^1.9.1",
|
"boardgamegeekclient": "^1.9.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "^0.6.0",
|
||||||
"drizzle-orm": "^0.30.1",
|
"drizzle-orm": "^0.30.2",
|
||||||
"feather-icons": "^4.29.1",
|
"feather-icons": "^4.29.1",
|
||||||
"formsnap": "^0.5.1",
|
"formsnap": "^0.5.1",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"iconify-icon": "^2.0.0",
|
"iconify-icon": "^2.0.0",
|
||||||
|
"just-capitalize": "^3.2.0",
|
||||||
"just-kebab-case": "^4.2.0",
|
"just-kebab-case": "^4.2.0",
|
||||||
"loader": "^2.1.1",
|
"loader": "^2.1.1",
|
||||||
"lucia": "3.1.1",
|
"lucia": "3.1.1",
|
||||||
"lucide-svelte": "^0.356.0",
|
"lucide-svelte": "^0.358.0",
|
||||||
"open-props": "^1.6.21",
|
"open-props": "^1.6.21",
|
||||||
"oslo": "^1.1.3",
|
"oslo": "^1.1.3",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
|
|
||||||
629
pnpm-lock.yaml
629
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
28
src/lib/components/ui/table/index.ts
Normal file
28
src/lib/components/ui/table/index.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import Root from "./table.svelte";
|
||||||
|
import Body from "./table-body.svelte";
|
||||||
|
import Caption from "./table-caption.svelte";
|
||||||
|
import Cell from "./table-cell.svelte";
|
||||||
|
import Footer from "./table-footer.svelte";
|
||||||
|
import Head from "./table-head.svelte";
|
||||||
|
import Header from "./table-header.svelte";
|
||||||
|
import Row from "./table-row.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Body,
|
||||||
|
Caption,
|
||||||
|
Cell,
|
||||||
|
Footer,
|
||||||
|
Head,
|
||||||
|
Header,
|
||||||
|
Row,
|
||||||
|
//
|
||||||
|
Root as Table,
|
||||||
|
Body as TableBody,
|
||||||
|
Caption as TableCaption,
|
||||||
|
Cell as TableCell,
|
||||||
|
Footer as TableFooter,
|
||||||
|
Head as TableHead,
|
||||||
|
Header as TableHeader,
|
||||||
|
Row as TableRow,
|
||||||
|
};
|
||||||
13
src/lib/components/ui/table/table-body.svelte
Normal file
13
src/lib/components/ui/table/table-body.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tbody class={cn("[&_tr:last-child]:border-0", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</tbody>
|
||||||
13
src/lib/components/ui/table/table-caption.svelte
Normal file
13
src/lib/components/ui/table/table-caption.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLTableCaptionElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<caption class={cn("mt-4 text-sm text-muted-foreground", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</caption>
|
||||||
18
src/lib/components/ui/table/table-cell.svelte
Normal file
18
src/lib/components/ui/table/table-cell.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLTdAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLTdAttributes;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</td>
|
||||||
13
src/lib/components/ui/table/table-footer.svelte
Normal file
13
src/lib/components/ui/table/table-footer.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tfoot class={cn("bg-primary font-medium text-primary-foreground", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</tfoot>
|
||||||
19
src/lib/components/ui/table/table-head.svelte
Normal file
19
src/lib/components/ui/table/table-head.svelte
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLThAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLThAttributes;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<th
|
||||||
|
class={cn(
|
||||||
|
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</th>
|
||||||
14
src/lib/components/ui/table/table-header.svelte
Normal file
14
src/lib/components/ui/table/table-header.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLTableSectionElement>;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<thead class={cn("[&_tr]:border-b", className)} {...$$restProps} on:click on:keydown>
|
||||||
|
<slot />
|
||||||
|
</thead>
|
||||||
23
src/lib/components/ui/table/table-row.svelte
Normal file
23
src/lib/components/ui/table/table-row.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLAttributes<HTMLTableRowElement> & {
|
||||||
|
"data-state"?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<tr
|
||||||
|
class={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
on:keydown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</tr>
|
||||||
15
src/lib/components/ui/table/table.svelte
Normal file
15
src/lib/components/ui/table/table.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import type { HTMLTableAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
type $$Props = HTMLTableAttributes;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full overflow-auto">
|
||||||
|
<table class={cn("w-full caption-bottom text-sm", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
@ -8,7 +8,7 @@ export const profileSchema = userSchema.pick({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const changeEmailSchema = userSchema.pick({
|
export const changeEmailSchema = userSchema.pick({
|
||||||
email: true,
|
email: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const changeUserPasswordSchema = z
|
export const changeUserPasswordSchema = z
|
||||||
|
|
@ -104,3 +104,11 @@ const checkPasswordStrength = async function (password: string, ctx: z.Refinemen
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const addRoleSchema = z.object({
|
||||||
|
roles: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||||
|
message: 'You have to select at least one item.'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AddRoleSchema = typeof addRoleSchema;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,32 @@
|
||||||
import { redirect } from 'sveltekit-flash-message/server'
|
import { redirect } from 'sveltekit-flash-message/server';
|
||||||
import type { PageServerLoad } from './$types';
|
|
||||||
import { notSignedInMessage } from '$lib/flashMessages';
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { user_roles } from '../../../../schema';
|
||||||
|
|
||||||
export async function load(event) {
|
export async function load(event) {
|
||||||
const { locals } = event;
|
const { locals } = event;
|
||||||
if (!locals?.user?.role?.includes('admin')) {
|
if (!locals?.user) {
|
||||||
redirect(302, '/login', notSignedInMessage, event);
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {}
|
const { user } = locals;
|
||||||
};
|
const userRoles = await db.query.user_roles.findMany({
|
||||||
|
where: eq(user_roles.user_id, user.id),
|
||||||
|
with: {
|
||||||
|
role: {
|
||||||
|
columns: {
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const containsAdminRole = userRoles.some((user_role) => user_role?.role?.name === 'admin');
|
||||||
|
if (!userRoles?.length || !containsAdminRole) {
|
||||||
|
console.log('Not an admin');
|
||||||
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,10 @@
|
||||||
|
<h1>Do the admin stuff</h1>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
:global(main) {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1 +1,7 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Button } from '$components/ui/button';
|
||||||
|
</script>
|
||||||
|
|
||||||
<h1>At the admin page yo!</h1>
|
<h1>At the admin page yo!</h1>
|
||||||
|
|
||||||
|
<Button href="/admin/users">Search for users</Button>
|
||||||
23
src/routes/(app)/(protected)/admin/users/+page.server.ts
Normal file
23
src/routes/(app)/(protected)/admin/users/+page.server.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { redirect } from "sveltekit-flash-message/server";
|
||||||
|
import type { PageServerLoad } from "./$types";
|
||||||
|
import { notSignedInMessage } from "$lib/flashMessages";
|
||||||
|
import db from "$lib/drizzle";
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async (event) => {
|
||||||
|
|
||||||
|
// TODO: Ensure admin user
|
||||||
|
if (!event.locals.user) {
|
||||||
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await db.query
|
||||||
|
.users
|
||||||
|
.findMany({
|
||||||
|
limit: 10,
|
||||||
|
offset: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
users
|
||||||
|
};
|
||||||
|
};
|
||||||
10
src/routes/(app)/(protected)/admin/users/+page.svelte
Normal file
10
src/routes/(app)/(protected)/admin/users/+page.svelte
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import DataTable from './user-table.svelte';
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Users</h1>
|
||||||
|
|
||||||
|
<div class="container mx-auto py-10">
|
||||||
|
<DataTable users={data?.users ?? []}/>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { eq, inArray, not } from 'drizzle-orm';
|
||||||
|
import { redirect } from 'sveltekit-flash-message/server';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { notSignedInMessage } from '$lib/flashMessages';
|
||||||
|
import db from '$lib/drizzle';
|
||||||
|
import { roles, users } from '../../../../../../schema';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async (event) => {
|
||||||
|
const { params } = event;
|
||||||
|
const { id } = params;
|
||||||
|
|
||||||
|
// TODO: Ensure admin user
|
||||||
|
if (!event.locals.user) {
|
||||||
|
redirect(302, '/login', notSignedInMessage, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundUser = await db.query.users.findFirst({
|
||||||
|
where: eq(users.cuid, id),
|
||||||
|
with: {
|
||||||
|
user_roles: {
|
||||||
|
with: {
|
||||||
|
role: {
|
||||||
|
columns: {
|
||||||
|
name: true,
|
||||||
|
cuid: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentRoleIds = foundUser?.user_roles?.map((user_role) => user_role?.role.cuid) || [];
|
||||||
|
|
||||||
|
console.log('currentRoleIds', currentRoleIds);
|
||||||
|
|
||||||
|
let availableRoles: { name: string; cuid: string }[] = [];
|
||||||
|
if (currentRoleIds?.length > 0) {
|
||||||
|
availableRoles = await db.query.roles.findMany({
|
||||||
|
where: not(inArray(roles.cuid, currentRoleIds)),
|
||||||
|
columns: {
|
||||||
|
name: true,
|
||||||
|
cuid: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: foundUser,
|
||||||
|
availableRoles
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
addRole: async (event) => {
|
||||||
|
const { params, request } = event;
|
||||||
|
d;
|
||||||
|
const data = await request.formData();
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
const roleCUID = data.get('value');
|
||||||
|
const dbRole = await db.query.roles.findFirst({ where: eq(roles.cuid, roleCUID?.toString()) });
|
||||||
|
console.log('dbRole', dbRole);
|
||||||
|
}
|
||||||
|
};
|
||||||
33
src/routes/(app)/(protected)/admin/users/[id]/+page.svelte
Normal file
33
src/routes/(app)/(protected)/admin/users/[id]/+page.svelte
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import capitalize from 'just-capitalize';
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
// import AddRolesForm from './add-roles-form.svelte';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
const { user, availableRoles } = data;
|
||||||
|
const { user_roles }: { user_roles: { role: { name: string, cuid: string } }[] } = user;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>User Details</h1>
|
||||||
|
<p>Username {user?.username}</p>
|
||||||
|
<p>Email Address: {user?.email || 'N/A'}</p>
|
||||||
|
<p>First Name: {user?.first_name || 'N/A'}</p>
|
||||||
|
<p>Last Name: {user?.last_name || 'N/A'}</p>
|
||||||
|
|
||||||
|
<h2>User Roles</h2>
|
||||||
|
{#each user_roles as user_role}
|
||||||
|
<p>{capitalize(user_role?.role?.name)}</p>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<h2>Roles Available to Assign</h2>
|
||||||
|
<!--<AddRolesForm {availableRoles} />-->
|
||||||
|
{#each availableRoles as role}
|
||||||
|
<form action="?/addRole" method="POST" use:enhance>
|
||||||
|
<div class="flex flex-row space-x-3 place-items-center mt-2">
|
||||||
|
<input type="hidden" name="role" value={role?.cuid} />
|
||||||
|
<Button type="submit">Add</Button>
|
||||||
|
<p>{capitalize(role?.name)}</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{/each}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import * as Form from "$lib/components/ui/form";
|
||||||
|
import { Input } from "$lib/components/ui/input";
|
||||||
|
import { Checkbox } from "$lib/components/ui/checkbox/index.js";
|
||||||
|
import { addRoleSchema, type AddRoleSchema } from '$lib/validations/account';
|
||||||
|
import {
|
||||||
|
type SuperValidated,
|
||||||
|
type Infer,
|
||||||
|
superForm,
|
||||||
|
} from "sveltekit-superforms";
|
||||||
|
import { zodClient } from "sveltekit-superforms/adapters";
|
||||||
|
|
||||||
|
export let availableRoles: { name: string; cuid: string }[] = [];
|
||||||
|
const data: SuperValidated<Infer<AddRoleSchema>> = availableRoles;
|
||||||
|
|
||||||
|
const form = superForm(data, {
|
||||||
|
validators: zodClient(addRoleSchema),
|
||||||
|
// onUpdated: ({ form: f }) => {
|
||||||
|
// if (f.valid) {
|
||||||
|
// toast.success("You submitted" + JSON.stringify(f.data, null, 2));
|
||||||
|
// } else {
|
||||||
|
// toast.error("Please fix the errors in the form.");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
const { form: formData, enhance } = form;
|
||||||
|
|
||||||
|
function addRole(id: string) {
|
||||||
|
$formData.roles = [...$formData.roles, id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeRole(id: string) {
|
||||||
|
$formData.roles = $formData.roles.filter((i) => i !== id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form action="/?/addMultipleRoles" method="POST" use:enhance>
|
||||||
|
<Form.Fieldset {form} name="roles" class="space-y-0">
|
||||||
|
<div class="mb-4">
|
||||||
|
<Form.Legend class="text-base">Roles</Form.Legend>
|
||||||
|
<Form.Description>
|
||||||
|
Select the roles you want to add to the user.
|
||||||
|
</Form.Description>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
{#each roles as item}
|
||||||
|
{@const checked = $formData.roles.includes(item.cuid)}
|
||||||
|
<div class="flex flex-row items-start space-x-3">
|
||||||
|
<Form.Control let:attrs>
|
||||||
|
<Checkbox
|
||||||
|
{...attrs}
|
||||||
|
{checked}
|
||||||
|
onCheckedChange={(v) => {
|
||||||
|
if (v) {
|
||||||
|
addItem(item.id);
|
||||||
|
} else {
|
||||||
|
removeItem(item.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Label class="text-sm font-normal">
|
||||||
|
{item.label}
|
||||||
|
</Form.Label>
|
||||||
|
<input
|
||||||
|
hidden
|
||||||
|
type="checkbox"
|
||||||
|
name={attrs.name}
|
||||||
|
value={item.id}
|
||||||
|
{checked}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<Form.FieldErrors />
|
||||||
|
</div>
|
||||||
|
</Form.Fieldset>
|
||||||
|
<Form.Button>Update display</Form.Button>
|
||||||
|
{#if browser}
|
||||||
|
<SuperDebug data={$formData} />
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Ellipsis from "lucide-svelte/icons/ellipsis";
|
||||||
|
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import { User } from 'lucide-svelte';
|
||||||
|
|
||||||
|
export let cuid: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger asChild let:builder>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
builders={[builder]}
|
||||||
|
size="icon"
|
||||||
|
class="relative h-8 w-8 p-0"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open menu</span>
|
||||||
|
<Ellipsis class="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content>
|
||||||
|
<DropdownMenu.Group>
|
||||||
|
<DropdownMenu.Label>Actions</DropdownMenu.Label>
|
||||||
|
<DropdownMenu.Item on:click={() => navigator.clipboard.writeText(cuid)}>
|
||||||
|
Copy User ID
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Group>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<a href={`/admin/users/${cuid}`}>
|
||||||
|
<DropdownMenu.Item>
|
||||||
|
<User class="mr-2 h-4 w-4" />
|
||||||
|
<span>View user</span>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</a>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Checkbox } from "$lib/components/ui/checkbox";
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export let checked: Writable<boolean>;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Checkbox bind:checked={$checked} />
|
||||||
232
src/routes/(app)/(protected)/admin/users/user-table.svelte
Normal file
232
src/routes/(app)/(protected)/admin/users/user-table.svelte
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
createTable,
|
||||||
|
Render,
|
||||||
|
Subscribe,
|
||||||
|
createRender,
|
||||||
|
} from "svelte-headless-table";
|
||||||
|
import {
|
||||||
|
addPagination,
|
||||||
|
addSortBy,
|
||||||
|
addTableFilter,
|
||||||
|
addHiddenColumns,
|
||||||
|
addSelectedRows,
|
||||||
|
} from "svelte-headless-table/plugins";
|
||||||
|
import { readable } from "svelte/store";
|
||||||
|
import ArrowUpDown from "lucide-svelte/icons/arrow-up-down";
|
||||||
|
import ChevronDown from "lucide-svelte/icons/chevron-down";
|
||||||
|
import * as Table from "$lib/components/ui/table";
|
||||||
|
import DataTableActions from "./user-table-actions.svelte";
|
||||||
|
import { Button } from "$lib/components/ui/button";
|
||||||
|
import { Input } from "$lib/components/ui/input";
|
||||||
|
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||||
|
import DataTableCheckbox from "./user-table-checkbox.svelte";
|
||||||
|
import type { Users } from '../../../../../schema';
|
||||||
|
|
||||||
|
export let users: Users[] = [];
|
||||||
|
|
||||||
|
const table = createTable(readable(users), {
|
||||||
|
page: addPagination(),
|
||||||
|
sort: addSortBy({ disableMultiSort: true }),
|
||||||
|
filter: addTableFilter({
|
||||||
|
fn: ({ filterValue, value }) => value.includes(filterValue),
|
||||||
|
}),
|
||||||
|
hide: addHiddenColumns(),
|
||||||
|
select: addSelectedRows(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = table.createColumns([
|
||||||
|
table.column({
|
||||||
|
accessor: "cuid",
|
||||||
|
header: (_, { pluginStates }) => {
|
||||||
|
const { allPageRowsSelected } = pluginStates.select;
|
||||||
|
return createRender(DataTableCheckbox, {
|
||||||
|
checked: allPageRowsSelected,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cell: ({ row }, { pluginStates }) => {
|
||||||
|
const { getRowState } = pluginStates.select;
|
||||||
|
const { isSelected } = getRowState(row);
|
||||||
|
|
||||||
|
return createRender(DataTableCheckbox, {
|
||||||
|
checked: isSelected,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
filter: {
|
||||||
|
exclude: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
table.column({
|
||||||
|
accessor: "username",
|
||||||
|
header: "Username",
|
||||||
|
}),
|
||||||
|
table.column({
|
||||||
|
accessor: "email",
|
||||||
|
header: "Email",
|
||||||
|
cell: ({ value }) => {
|
||||||
|
return value ?? "N/A";
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
table.column({
|
||||||
|
accessor: "first_name",
|
||||||
|
header: "First Name",
|
||||||
|
cell: ({ value }) => {
|
||||||
|
return value && value.length > 0 ? value : "N/A";
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
filter: {
|
||||||
|
exclude: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
table.column({
|
||||||
|
accessor: "last_name",
|
||||||
|
header: "Last Name",
|
||||||
|
cell: ({ value }) => {
|
||||||
|
return value && value.length > 0 ? value : "N/A";
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
filter: {
|
||||||
|
exclude: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
table.column({
|
||||||
|
accessor: ({ cuid }) => cuid,
|
||||||
|
header: "",
|
||||||
|
cell: ({ value }) => {
|
||||||
|
return createRender(DataTableActions, { cuid: value });
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
sort: {
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
headerRows,
|
||||||
|
pageRows,
|
||||||
|
tableAttrs,
|
||||||
|
tableBodyAttrs,
|
||||||
|
pluginStates,
|
||||||
|
flatColumns,
|
||||||
|
rows,
|
||||||
|
} = table.createViewModel(columns);
|
||||||
|
|
||||||
|
const { pageIndex, hasNextPage, hasPreviousPage } = pluginStates.page;
|
||||||
|
const { filterValue } = pluginStates.filter;
|
||||||
|
const { hiddenColumnIds } = pluginStates.hide;
|
||||||
|
const { selectedDataIds } = pluginStates.select;
|
||||||
|
|
||||||
|
const ids = flatColumns.map((col) => col.id);
|
||||||
|
let hideForId = Object.fromEntries(ids.map((id) => [id, true]));
|
||||||
|
|
||||||
|
$: $hiddenColumnIds = Object.entries(hideForId)
|
||||||
|
.filter(([, hide]) => !hide)
|
||||||
|
.map(([id]) => id);
|
||||||
|
|
||||||
|
const columnsToHide: string[] = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center py-4">
|
||||||
|
<Input
|
||||||
|
class="max-w-sm"
|
||||||
|
placeholder="Filter emails..."
|
||||||
|
type="text"
|
||||||
|
bind:value={$filterValue}
|
||||||
|
/>
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger asChild let:builder>
|
||||||
|
<Button variant="outline" class="ml-auto" builders={[builder]}>
|
||||||
|
Columns <ChevronDown class="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content>
|
||||||
|
{#each flatColumns as col}
|
||||||
|
{#if columnsToHide.includes(col.id)}
|
||||||
|
<DropdownMenu.CheckboxItem bind:checked={hideForId[col.id]}>
|
||||||
|
{col.header}
|
||||||
|
</DropdownMenu.CheckboxItem>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-md border">
|
||||||
|
<Table.Root {...$tableAttrs}>
|
||||||
|
<Table.Header>
|
||||||
|
{#each $headerRows as headerRow}
|
||||||
|
<Subscribe rowAttrs={headerRow.attrs()}>
|
||||||
|
<Table.Row>
|
||||||
|
{#each headerRow.cells as cell (cell.id)}
|
||||||
|
<Subscribe
|
||||||
|
attrs={cell.attrs()}
|
||||||
|
let:attrs
|
||||||
|
props={cell.props()}
|
||||||
|
let:props
|
||||||
|
>
|
||||||
|
<Table.Head {...attrs} class="[&:has([role=checkbox])]:pl-3">
|
||||||
|
{#if cell.id === "email"}
|
||||||
|
<Button variant="ghost" on:click={props.sort.toggle}>
|
||||||
|
<Render of={cell.render()} />
|
||||||
|
<ArrowUpDown class={"ml-2 h-4 w-4"} />
|
||||||
|
</Button>
|
||||||
|
{:else if cell.id === "username"}
|
||||||
|
<Button variant="ghost" on:click={props.sort.toggle}>
|
||||||
|
<Render of={cell.render()} />
|
||||||
|
<ArrowUpDown class={"ml-2 h-4 w-4"} />
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Render of={cell.render()} />
|
||||||
|
{/if}
|
||||||
|
</Table.Head>
|
||||||
|
</Subscribe>
|
||||||
|
{/each}
|
||||||
|
</Table.Row>
|
||||||
|
</Subscribe>
|
||||||
|
{/each}
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body {...$tableBodyAttrs}>
|
||||||
|
{#each $pageRows as row (row.id)}
|
||||||
|
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
|
||||||
|
<Table.Row
|
||||||
|
{...rowAttrs}
|
||||||
|
data-state={$selectedDataIds[row.id] && "selected"}
|
||||||
|
>
|
||||||
|
{#each row.cells as cell (cell.id)}
|
||||||
|
<Subscribe attrs={cell.attrs()} let:attrs>
|
||||||
|
<Table.Cell {...attrs} class="[&:has([role=checkbox])]:pl-3">
|
||||||
|
<Render of={cell.render()} />
|
||||||
|
</Table.Cell>
|
||||||
|
</Subscribe>
|
||||||
|
{/each}
|
||||||
|
</Table.Row>
|
||||||
|
</Subscribe>
|
||||||
|
{/each}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-end space-x-4 py-4">
|
||||||
|
<div class="flex-1 text-sm text-muted-foreground">
|
||||||
|
{Object.keys($selectedDataIds).length} of{" "}
|
||||||
|
{$rows.length} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
on:click={() => ($pageIndex = $pageIndex - 1)}
|
||||||
|
disabled={!$hasPreviousPage}>Previous</Button
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={!$hasNextPage}
|
||||||
|
on:click={() => ($pageIndex = $pageIndex + 1)}>Next</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -29,7 +29,12 @@ const signUpDefaults = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
// redirect(302, '/waitlist', { type: 'error', message: 'Sign-up not yet available. Please add your email to the waitlist!' }, event);
|
redirect(
|
||||||
|
302,
|
||||||
|
'/waitlist',
|
||||||
|
{ type: 'error', message: 'Sign-up not yet available. Please add your email to the waitlist!' },
|
||||||
|
event
|
||||||
|
);
|
||||||
|
|
||||||
if (event.locals.user) {
|
if (event.locals.user) {
|
||||||
const message = { type: 'success', message: 'You are already signed in' } as const;
|
const message = { type: 'success', message: 'You are already signed in' } as const;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { tsvector } from './tsVector';
|
||||||
// User Related Schemas
|
// User Related Schemas
|
||||||
|
|
||||||
export const users = pgTable('users', {
|
export const users = pgTable('users', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -52,11 +52,12 @@ export const sessions = pgTable('sessions', {
|
||||||
});
|
});
|
||||||
|
|
||||||
export const roles = pgTable('roles', {
|
export const roles = pgTable('roles', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2())
|
||||||
name: text('name').unique()
|
.notNull(),
|
||||||
|
name: text('name').unique().notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Roles = InferSelectModel<typeof roles>;
|
export type Roles = InferSelectModel<typeof roles>;
|
||||||
|
|
@ -66,7 +67,7 @@ export const role_relations = relations(roles, ({ many }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const user_roles = pgTable('user_roles', {
|
export const user_roles = pgTable('user_roles', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -116,7 +117,7 @@ export const password_reset_token_relations = relations(password_reset_tokens, (
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const collections = pgTable('collections', {
|
export const collections = pgTable('collections', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -135,7 +136,7 @@ export const collection_relations = relations(collections, ({ one }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const collection_items = pgTable('collection_items', {
|
export const collection_items = pgTable('collection_items', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -164,7 +165,7 @@ export const collection_item_relations = relations(collection_items, ({ one }) =
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const wishlists = pgTable('wishlists', {
|
export const wishlists = pgTable('wishlists', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -185,7 +186,7 @@ export const wishlists_relations = relations(wishlists, ({ one }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const wishlist_items = pgTable('wishlist_items', {
|
export const wishlist_items = pgTable('wishlist_items', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -224,7 +225,7 @@ export const externalIdType = pgEnum('external_id_type', [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const externalIds = pgTable('external_ids', {
|
export const externalIds = pgTable('external_ids', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -237,7 +238,7 @@ export type ExternalIds = InferSelectModel<typeof externalIds>;
|
||||||
export const games = pgTable(
|
export const games = pgTable(
|
||||||
'games',
|
'games',
|
||||||
{
|
{
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -301,7 +302,7 @@ export const gameRelations = relations(games, ({ many }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const expansions = pgTable('expansions', {
|
export const expansions = pgTable('expansions', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -329,7 +330,7 @@ export const expansion_relations = relations(expansions, ({ one }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const publishers = pgTable('publishers', {
|
export const publishers = pgTable('publishers', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -366,7 +367,7 @@ export const publishers_relations = relations(publishers, ({ many }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const categories = pgTable('categories', {
|
export const categories = pgTable('categories', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
@ -433,7 +434,7 @@ export const categories_relations = relations(categories, ({ many }) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const mechanics = pgTable('mechanics', {
|
export const mechanics = pgTable('mechanics', {
|
||||||
id: uuid('id').primaryKey(),
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
cuid: text('cuid')
|
cuid: text('cuid')
|
||||||
.unique()
|
.unique()
|
||||||
.$defaultFn(() => cuid2()),
|
.$defaultFn(() => cuid2()),
|
||||||
|
|
|
||||||
34
src/seed.ts
34
src/seed.ts
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import { drizzle } from "drizzle-orm/node-postgres";
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import pg from 'pg';
|
import pg from 'pg';
|
||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
|
|
||||||
|
|
@ -10,24 +10,32 @@ const pool = new pg.Pool({
|
||||||
host: process.env.DATABASE_HOST,
|
host: process.env.DATABASE_HOST,
|
||||||
port: new Number(process.env.DATABASE_PORT).valueOf(),
|
port: new Number(process.env.DATABASE_PORT).valueOf(),
|
||||||
database: process.env.DATABASE_DB,
|
database: process.env.DATABASE_DB,
|
||||||
ssl: process.env.DATABASE_HOST === 'localhost' ? false : true,
|
ssl: process.env.DATABASE_HOST === 'localhost' ? false : true
|
||||||
});
|
});
|
||||||
|
|
||||||
const db = drizzle(pool, { schema: schema });
|
const db = drizzle(pool, { schema: schema });
|
||||||
|
|
||||||
const existingRoles = await db.query.roles.findMany();
|
const existingRoles = await db.query.roles.findMany();
|
||||||
console.log('Existing roles', existingRoles);
|
console.log('Existing roles', existingRoles);
|
||||||
if (existingRoles.length === 0) {
|
console.log('Creating roles ...');
|
||||||
console.log('Creating roles ...');
|
await db
|
||||||
await db.insert(schema.roles).values([{
|
.insert(schema.roles)
|
||||||
name: 'admin'
|
.values([{ name: 'admin' }])
|
||||||
}, {
|
.onConflictDoNothing();
|
||||||
name: 'user'
|
await db
|
||||||
}]);
|
.insert(schema.roles)
|
||||||
console.log('Roles created.');
|
.values([{ name: 'user' }])
|
||||||
} else {
|
.onConflictDoNothing();
|
||||||
console.log('Roles already exist. No action taken.');
|
await db
|
||||||
}
|
.insert(schema.roles)
|
||||||
|
.values([{ name: 'editor' }])
|
||||||
|
.onConflictDoNothing();
|
||||||
|
console.log('Roles created.');
|
||||||
|
await db
|
||||||
|
.insert(schema.roles)
|
||||||
|
.values([{ name: 'moderator' }])
|
||||||
|
.onConflictDoNothing();
|
||||||
|
console.log('Roles created.');
|
||||||
|
|
||||||
await pool.end();
|
await pool.end();
|
||||||
process.exit();
|
process.exit();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue