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:
Bradley Shellnut 2024-03-15 12:05:47 -07:00
parent c1aaad7f6c
commit 5e174c875f
31 changed files with 4148 additions and 335 deletions

View 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();

View 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;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,20 @@
"when": 1710268371021,
"tag": "0003_mushy_madame_masque",
"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
}
]
}

View file

@ -26,11 +26,11 @@
"@playwright/test": "^1.42.1",
"@resvg/resvg-js": "^2.6.0",
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/enhanced-img": "^0.1.8",
"@sveltejs/kit": "^2.5.3",
"@sveltejs/enhanced-img": "^0.1.9",
"@sveltejs/kit": "^2.5.4",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/cookie": "^0.6.0",
"@types/node": "^20.11.26",
"@types/node": "^20.11.27",
"@types/pg": "^8.11.2",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
@ -45,20 +45,21 @@
"postcss": "^8.4.35",
"postcss-import": "^16.0.1",
"postcss-load-config": "^5.0.3",
"postcss-preset-env": "^9.5.0",
"postcss-preset-env": "^9.5.1",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.2",
"sass": "^1.71.1",
"sass": "^1.72.0",
"satori": "^0.10.13",
"satori-html": "^0.3.2",
"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-preprocess": "^5.1.3",
"svelte-sequential-preprocessor": "^2.0.1",
"sveltekit-flash-message": "^2.4.4",
"sveltekit-rate-limiter": "^0.4.3",
"sveltekit-superforms": "^2.8.1",
"sveltekit-superforms": "^2.9.0",
"tailwindcss": "^3.4.1",
"ts-node": "^10.9.2",
"tslib": "^2.6.1",
@ -86,20 +87,21 @@
"@sveltejs/adapter-vercel": "^5.1.1",
"@types/feather-icons": "^4.29.4",
"@vercel/og": "^0.5.20",
"bits-ui": "^0.19.6",
"bits-ui": "^0.19.7",
"boardgamegeekclient": "^1.9.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cookie": "^0.6.0",
"drizzle-orm": "^0.30.1",
"drizzle-orm": "^0.30.2",
"feather-icons": "^4.29.1",
"formsnap": "^0.5.1",
"html-entities": "^2.5.2",
"iconify-icon": "^2.0.0",
"just-capitalize": "^3.2.0",
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",
"lucia": "3.1.1",
"lucide-svelte": "^0.356.0",
"lucide-svelte": "^0.358.0",
"open-props": "^1.6.21",
"oslo": "^1.1.3",
"pg": "^8.11.3",

File diff suppressed because it is too large Load diff

View 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,
};

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View file

@ -8,7 +8,7 @@ export const profileSchema = userSchema.pick({
});
export const changeEmailSchema = userSchema.pick({
email: true,
email: true
});
export const changeUserPasswordSchema = z
@ -103,4 +103,12 @@ const checkPasswordStrength = async function (password: string, ctx: z.Refinemen
path: ['password']
});
}
};
};
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;

View file

@ -1,12 +1,32 @@
import { redirect } from 'sveltekit-flash-message/server'
import type { PageServerLoad } from './$types';
import { redirect } from 'sveltekit-flash-message/server';
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) {
const { locals } = event;
if (!locals?.user?.role?.includes('admin')) {
if (!locals?.user) {
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 {};
}

View file

@ -1 +1,10 @@
<slot />
<h1>Do the admin stuff</h1>
<slot />
<style lang="postcss">
:global(main) {
margin: 0;
max-width: 100vw;
}
</style>

View file

@ -1 +1,7 @@
<script lang="ts">
import { Button } from '$components/ui/button';
</script>
<h1>At the admin page yo!</h1>
<Button href="/admin/users">Search for users</Button>

View 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
};
};

View 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>

View file

@ -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);
}
};

View 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}

View file

@ -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>

View file

@ -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>

View file

@ -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} />

View 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>

View file

@ -29,7 +29,12 @@ const signUpDefaults = {
};
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) {
const message = { type: 'success', message: 'You are already signed in' } as const;

View file

@ -16,7 +16,7 @@ import { tsvector } from './tsVector';
// User Related Schemas
export const users = pgTable('users', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -52,11 +52,12 @@ export const sessions = pgTable('sessions', {
});
export const roles = pgTable('roles', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
name: text('name').unique()
.$defaultFn(() => cuid2())
.notNull(),
name: text('name').unique().notNull()
});
export type Roles = InferSelectModel<typeof roles>;
@ -66,7 +67,7 @@ export const role_relations = relations(roles, ({ many }) => ({
}));
export const user_roles = pgTable('user_roles', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -116,7 +117,7 @@ export const password_reset_token_relations = relations(password_reset_tokens, (
}));
export const collections = pgTable('collections', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -135,7 +136,7 @@ export const collection_relations = relations(collections, ({ one }) => ({
}));
export const collection_items = pgTable('collection_items', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -164,7 +165,7 @@ export const collection_item_relations = relations(collection_items, ({ one }) =
}));
export const wishlists = pgTable('wishlists', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -185,7 +186,7 @@ export const wishlists_relations = relations(wishlists, ({ one }) => ({
}));
export const wishlist_items = pgTable('wishlist_items', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -224,7 +225,7 @@ export const externalIdType = pgEnum('external_id_type', [
]);
export const externalIds = pgTable('external_ids', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -237,7 +238,7 @@ export type ExternalIds = InferSelectModel<typeof externalIds>;
export const games = pgTable(
'games',
{
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -301,7 +302,7 @@ export const gameRelations = relations(games, ({ many }) => ({
}));
export const expansions = pgTable('expansions', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -329,7 +330,7 @@ export const expansion_relations = relations(expansions, ({ one }) => ({
}));
export const publishers = pgTable('publishers', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -366,7 +367,7 @@ export const publishers_relations = relations(publishers, ({ many }) => ({
}));
export const categories = pgTable('categories', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),
@ -433,7 +434,7 @@ export const categories_relations = relations(categories, ({ many }) => ({
}));
export const mechanics = pgTable('mechanics', {
id: uuid('id').primaryKey(),
id: uuid('id').primaryKey().defaultRandom(),
cuid: text('cuid')
.unique()
.$defaultFn(() => cuid2()),

View file

@ -1,5 +1,5 @@
import 'dotenv/config';
import { drizzle } from "drizzle-orm/node-postgres";
import { drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from './schema';
@ -10,24 +10,32 @@ const pool = new pg.Pool({
host: process.env.DATABASE_HOST,
port: new Number(process.env.DATABASE_PORT).valueOf(),
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 existingRoles = await db.query.roles.findMany();
console.log('Existing roles', existingRoles);
if (existingRoles.length === 0) {
console.log('Creating roles ...');
await db.insert(schema.roles).values([{
name: 'admin'
}, {
name: 'user'
}]);
console.log('Roles created.');
} else {
console.log('Roles already exist. No action taken.');
}
console.log('Creating roles ...');
await db
.insert(schema.roles)
.values([{ name: 'admin' }])
.onConflictDoNothing();
await db
.insert(schema.roles)
.values([{ name: 'user' }])
.onConflictDoNothing();
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();
process.exit();