admin stuff
This commit is contained in:
parent
3bf4c7ce04
commit
1f5de32f61
@ -19,3 +19,4 @@ EMAIL_SMTP_PASS=
|
||||
REGISTRATIONS=true
|
||||
ADDRESS_HEADER=X-Forwarded-For
|
||||
XFF_DEPTH=1
|
||||
AUTO_MIGRATE=true
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
export default defineConfig({
|
||||
dialect: 'mysql',
|
||||
schema: './src/lib/server/drizzle/schema.ts',
|
||||
out: './src/lib/server/drizzle/migrations',
|
||||
dialect: 'mysql',
|
||||
schema: './src/lib/server/drizzle/schema.ts',
|
||||
out: './migrations',
|
||||
dbCredentials: {
|
||||
host: process.env.DATABASE_HOST as string,
|
||||
port: Number(process.env.DATABASE_PORT) || 3306,
|
||||
@ -13,4 +13,4 @@ export default defineConfig({
|
||||
},
|
||||
verbose: true,
|
||||
strict: true
|
||||
})
|
||||
});
|
||||
|
@ -1,5 +1,3 @@
|
||||
-- Current sql file was generated after introspecting the database
|
||||
-- If you want to run this migration please uncomment this code before executing migrations
|
||||
/*
|
||||
CREATE TABLE `audit_log` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
@ -11,7 +9,7 @@ CREATE TABLE `audit_log` (
|
||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||
`actorId` int(11) DEFAULT 'NULL'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `document` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
@ -21,7 +19,7 @@ CREATE TABLE `document` (
|
||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `o_auth2_client` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`client_id` varchar(36) NOT NULL,
|
||||
@ -38,7 +36,7 @@ CREATE TABLE `o_auth2_client` (
|
||||
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||
CONSTRAINT `IDX_e9d16c213910ad57bd05e97b42` UNIQUE(`client_id`)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `o_auth2_client_authorization` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`scope` text DEFAULT 'NULL',
|
||||
@ -47,7 +45,7 @@ CREATE TABLE `o_auth2_client_authorization` (
|
||||
`userId` int(11) DEFAULT 'NULL',
|
||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `o_auth2_client_url` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`url` varchar(255) NOT NULL,
|
||||
@ -56,7 +54,7 @@ CREATE TABLE `o_auth2_client_url` (
|
||||
`updated_at` timestamp(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||
`clientId` int(11) DEFAULT 'NULL'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `o_auth2_token` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`type` enum('code','access_token','refresh_token') NOT NULL,
|
||||
@ -70,12 +68,12 @@ CREATE TABLE `o_auth2_token` (
|
||||
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||
`pcke` text DEFAULT 'NULL'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `privilege` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`name` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `upload` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`original_name` varchar(255) NOT NULL,
|
||||
@ -85,7 +83,7 @@ CREATE TABLE `upload` (
|
||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `user` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`uuid` varchar(36) NOT NULL,
|
||||
@ -102,12 +100,12 @@ CREATE TABLE `user` (
|
||||
CONSTRAINT `IDX_78a916df40e02a9deb1c4b75ed` UNIQUE(`username`),
|
||||
CONSTRAINT `IDX_e12875dfb3b1d92d7d7c5377e2` UNIQUE(`email`)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `user_privileges_privilege` (
|
||||
`userId` int(11) NOT NULL,
|
||||
`privilegeId` int(11) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE TABLE `user_token` (
|
||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
@ -117,21 +115,21 @@ CREATE TABLE `user_token` (
|
||||
`nonce` text DEFAULT 'NULL',
|
||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE `audit_log` ADD CONSTRAINT `FK_cb6aa6f6fd56f08eafb60316225` FOREIGN KEY (`actorId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `document` ADD CONSTRAINT `FK_6a2eb13cadfc503989cbe367572` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_4a6c878506b872e85b3d07f6252` FOREIGN KEY (`ownerId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_e8d65b1eec13474e493420517d7` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_8227110f58510b7233f3db90cfb` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_9ca9ebb654e7ce71954d5fdb281` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_url` ADD CONSTRAINT `FK_aca59c7bdd65987487eea98d00f` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_3ecb760b321ef9bbab635f05b45` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_81ffb9b8d672cf3af1af9e789f3` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `upload` ADD CONSTRAINT `FK_7b8d52838a953b188255682597b` FOREIGN KEY (`uploaderId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user` ADD CONSTRAINT `FK_7478a15985dbfa32ed5fc77a7a1` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_0664a7ff494a1859a09014c0f17` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_e71171f4ed20bc8564a1819d0b7` FOREIGN KEY (`privilegeId`) REFERENCES `privilege`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user_token` ADD CONSTRAINT `FK_d37db50eecdf9b8ce4eedd2f918` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX `IDX_0664a7ff494a1859a09014c0f1` ON `user_privileges_privilege` (`userId`);--> statement-breakpoint
|
||||
|
||||
ALTER TABLE `audit_log` ADD CONSTRAINT `FK_cb6aa6f6fd56f08eafb60316225` FOREIGN KEY (`actorId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE `document` ADD CONSTRAINT `FK_6a2eb13cadfc503989cbe367572` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE no action ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_4a6c878506b872e85b3d07f6252` FOREIGN KEY (`ownerId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `FK_e8d65b1eec13474e493420517d7` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_8227110f58510b7233f3db90cfb` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `FK_9ca9ebb654e7ce71954d5fdb281` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_client_url` ADD CONSTRAINT `FK_aca59c7bdd65987487eea98d00f` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_3ecb760b321ef9bbab635f05b45` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `FK_81ffb9b8d672cf3af1af9e789f3` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE `upload` ADD CONSTRAINT `FK_7b8d52838a953b188255682597b` FOREIGN KEY (`uploaderId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE cascade;
|
||||
ALTER TABLE `user` ADD CONSTRAINT `FK_7478a15985dbfa32ed5fc77a7a1` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE cascade;
|
||||
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_0664a7ff494a1859a09014c0f17` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE cascade;
|
||||
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `FK_e71171f4ed20bc8564a1819d0b7` FOREIGN KEY (`privilegeId`) REFERENCES `privilege`(`id`) ON DELETE cascade ON UPDATE cascade;
|
||||
ALTER TABLE `user_token` ADD CONSTRAINT `FK_d37db50eecdf9b8ce4eedd2f918` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX `IDX_0664a7ff494a1859a09014c0f1` ON `user_privileges_privilege` (`userId`);
|
||||
CREATE INDEX `IDX_e71171f4ed20bc8564a1819d0b` ON `user_privileges_privilege` (`privilegeId`);
|
||||
*/
|
67
migrations/0001_redundant_layla_miller.sql
Normal file
67
migrations/0001_redundant_layla_miller.sql
Normal file
@ -0,0 +1,67 @@
|
||||
CREATE TABLE `o_auth2_client_manager` (
|
||||
`id` int AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
`clientId` int NOT NULL,
|
||||
`userId` int NOT NULL,
|
||||
`issuerId` int,
|
||||
`created_at` datetime(6) NOT NULL DEFAULT current_timestamp(6),
|
||||
`updated_at` datetime(6) NOT NULL DEFAULT current_timestamp(6)
|
||||
);--> statement-breakpoint
|
||||
DROP TABLE `migrations`;--> statement-breakpoint
|
||||
ALTER TABLE `audit_log` DROP FOREIGN KEY `FK_cb6aa6f6fd56f08eafb60316225`;--> statement-breakpoint
|
||||
ALTER TABLE `document` DROP FOREIGN KEY `FK_6a2eb13cadfc503989cbe367572`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` DROP FOREIGN KEY `FK_4a6c878506b872e85b3d07f6252`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` DROP FOREIGN KEY `FK_e8d65b1eec13474e493420517d7`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` DROP FOREIGN KEY `FK_8227110f58510b7233f3db90cfb`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` DROP FOREIGN KEY `FK_9ca9ebb654e7ce71954d5fdb281`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_url` DROP FOREIGN KEY `FK_aca59c7bdd65987487eea98d00f`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` DROP FOREIGN KEY `FK_3ecb760b321ef9bbab635f05b45`;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` DROP FOREIGN KEY `FK_81ffb9b8d672cf3af1af9e789f3`;--> statement-breakpoint
|
||||
ALTER TABLE `upload` DROP FOREIGN KEY `FK_7b8d52838a953b188255682597b`;--> statement-breakpoint
|
||||
ALTER TABLE `user` DROP FOREIGN KEY `FK_7478a15985dbfa32ed5fc77a7a1`;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` DROP FOREIGN KEY `FK_0664a7ff494a1859a09014c0f17`;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` DROP FOREIGN KEY `FK_e71171f4ed20bc8564a1819d0b7`;--> statement-breakpoint
|
||||
ALTER TABLE `user_token` DROP FOREIGN KEY `FK_d37db50eecdf9b8ce4eedd2f918`;--> statement-breakpoint
|
||||
ALTER TABLE `audit_log` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `audit_log` MODIFY COLUMN `actorId` int;--> statement-breakpoint
|
||||
ALTER TABLE `document` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `document` MODIFY COLUMN `authorId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` MODIFY COLUMN `pictureId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` MODIFY COLUMN `ownerId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` MODIFY COLUMN `clientId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` MODIFY COLUMN `userId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_url` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_url` MODIFY COLUMN `clientId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` MODIFY COLUMN `userId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` MODIFY COLUMN `clientId` int;--> statement-breakpoint
|
||||
ALTER TABLE `privilege` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `upload` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `upload` MODIFY COLUMN `uploaderId` int;--> statement-breakpoint
|
||||
ALTER TABLE `user` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `user` MODIFY COLUMN `pictureId` int;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` MODIFY COLUMN `userId` int NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` MODIFY COLUMN `privilegeId` int NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `user_token` MODIFY COLUMN `id` int AUTO_INCREMENT NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `user_token` MODIFY COLUMN `userId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD `current` tinyint DEFAULT 1 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE `privilege` ADD `clientId` int;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_manager` ADD CONSTRAINT `o_auth2_client_manager_clientId_o_auth2_client_id_fk` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_manager` ADD CONSTRAINT `o_auth2_client_manager_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_manager` ADD CONSTRAINT `o_auth2_client_manager_issuerId_user_id_fk` FOREIGN KEY (`issuerId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `audit_log` ADD CONSTRAINT `audit_log_actorId_user_id_fk` FOREIGN KEY (`actorId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `document` ADD CONSTRAINT `document_authorId_user_id_fk` FOREIGN KEY (`authorId`) REFERENCES `user`(`id`) ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `o_auth2_client_pictureId_upload_id_fk` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client` ADD CONSTRAINT `o_auth2_client_ownerId_user_id_fk` FOREIGN KEY (`ownerId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `o_auth2_client_authorization_clientId_o_auth2_client_id_fk` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_authorization` ADD CONSTRAINT `o_auth2_client_authorization_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_client_url` ADD CONSTRAINT `o_auth2_client_url_clientId_o_auth2_client_id_fk` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `o_auth2_token_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `o_auth2_token` ADD CONSTRAINT `o_auth2_token_clientId_o_auth2_client_id_fk` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `privilege` ADD CONSTRAINT `privilege_clientId_o_auth2_client_id_fk` FOREIGN KEY (`clientId`) REFERENCES `o_auth2_client`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE `upload` ADD CONSTRAINT `upload_uploaderId_user_id_fk` FOREIGN KEY (`uploaderId`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user` ADD CONSTRAINT `user_pictureId_upload_id_fk` FOREIGN KEY (`pictureId`) REFERENCES `upload`(`id`) ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `user_privileges_privilege_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user_privileges_privilege` ADD CONSTRAINT `user_privileges_privilege_privilegeId_privilege_id_fk` FOREIGN KEY (`privilegeId`) REFERENCES `privilege`(`id`) ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
|
||||
ALTER TABLE `user_token` ADD CONSTRAINT `user_token_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;
|
1049
migrations/meta/0001_snapshot.json
Normal file
1049
migrations/meta/0001_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,13 @@
|
||||
"when": 1715871691221,
|
||||
"tag": "0000_initial",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1717232859846,
|
||||
"tag": "0001_redundant_layla_miller",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
1
src/app.d.ts
vendored
1
src/app.d.ts
vendored
@ -15,6 +15,7 @@ declare global {
|
||||
interface Locals {
|
||||
session: Session<SessionData>;
|
||||
user: User;
|
||||
privileges: string[];
|
||||
}
|
||||
|
||||
interface PageData {
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { SESSION_SECRET } from '$env/static/private';
|
||||
import '$lib/server/drizzle';
|
||||
import { AUTO_MIGRATE, SESSION_SECRET } from '$env/static/private';
|
||||
import { db } from '$lib/server/drizzle';
|
||||
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
||||
import { handleSession } from 'svelte-kit-cookie-session';
|
||||
|
||||
if (AUTO_MIGRATE === 'true') {
|
||||
await migrate(db, { migrationsFolder: './migrations' });
|
||||
}
|
||||
|
||||
export const handle = handleSession({
|
||||
secret: SESSION_SECRET
|
||||
});
|
||||
|
73
src/lib/components/Paginator.svelte
Normal file
73
src/lib/components/Paginator.svelte
Normal file
@ -0,0 +1,73 @@
|
||||
<script lang="ts">
|
||||
import type { PaginationMeta } from '$lib/types';
|
||||
import { page } from '$app/stores';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
export let meta: PaginationMeta;
|
||||
$: pageNum = Number($page.url.searchParams.get('page')) || 1;
|
||||
$: firstPage = pageNum === 1;
|
||||
$: lastPage = pageNum === meta.pageCount;
|
||||
$: pageButtons = Array.from({ length: meta.pageCount }, (_, i) => i + 1);
|
||||
</script>
|
||||
|
||||
<nav class="pager">
|
||||
<a
|
||||
class="page-button page-prev {firstPage ? 'disabled' : ''}"
|
||||
href={`?page=${pageNum - 1}`}
|
||||
tabindex={firstPage ? -1 : 0}
|
||||
aria-label={$t('common.previous')}
|
||||
aria-disabled={firstPage}><<</a
|
||||
>
|
||||
|
||||
{#each pageButtons as buttonNumber}
|
||||
{@const active = buttonNumber === pageNum}
|
||||
<a
|
||||
class="page-button page-link {active ? 'disabled' : ''}"
|
||||
href={`?page=${buttonNumber}`}
|
||||
tabindex={active ? -1 : 0}
|
||||
aria-label={`${$t('common.page')} ${buttonNumber}`}
|
||||
aria-disabled={active}>{buttonNumber}</a
|
||||
>
|
||||
{/each}
|
||||
|
||||
<a
|
||||
class="page-button page-prev {lastPage ? 'disabled' : ''}"
|
||||
tabindex={lastPage ? -1 : 0}
|
||||
href={`?page=${pageNum + 1}`}
|
||||
aria-label={$t('common.next')}
|
||||
aria-disabled={lastPage}>>></a
|
||||
>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-button {
|
||||
--in-page-button-size: 28px;
|
||||
display: block;
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
min-width: var(--in-page-button-size);
|
||||
height: var(--in-page-button-size);
|
||||
line-height: var(--in-page-button-size);
|
||||
text-align: center;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
background-color: #ececec;
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
</style>
|
44
src/lib/components/admin/AdminHeader.svelte
Normal file
44
src/lib/components/admin/AdminHeader.svelte
Normal file
@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { PUBLIC_SITE_NAME } from '$env/static/public';
|
||||
import type { UserSession } from '$lib/types';
|
||||
export let user: UserSession;
|
||||
</script>
|
||||
|
||||
<header class="admin-header">
|
||||
<a class="site-name" href="/">{PUBLIC_SITE_NAME}</a>
|
||||
<div class="admin-user">
|
||||
<img class="admin-user-avatar" src={`/api/avatar/${user.uuid}`} alt={user.name} />
|
||||
<span class="admin-user-name">{user.name}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.admin-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #00aaff;
|
||||
background: linear-gradient(180deg, #00aaff 0%, #005bff 100%);
|
||||
}
|
||||
|
||||
.admin-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 8px 4px 4px;
|
||||
background-color: #004edf;
|
||||
border-radius: 40px;
|
||||
|
||||
& .admin-user-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
79
src/lib/components/admin/AdminSidebar.svelte
Normal file
79
src/lib/components/admin/AdminSidebar.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import type { UserSession } from '$lib/types';
|
||||
import { hasPrivileges } from '$lib/utils';
|
||||
import { page } from '$app/stores';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
export let user: UserSession;
|
||||
|
||||
const links = [
|
||||
{
|
||||
href: '/ssoadmin/users',
|
||||
title: $t('admin.menu.users'),
|
||||
privileges: ['admin:user']
|
||||
},
|
||||
{
|
||||
href: '/ssoadmin/oauth2',
|
||||
title: $t('admin.menu.oauth2'),
|
||||
privileges: [['admin:oauth2', 'self:oauth2']]
|
||||
},
|
||||
{
|
||||
href: '/ssoadmin/audit',
|
||||
title: $t('admin.menu.audit'),
|
||||
privileges: ['admin:audit']
|
||||
}
|
||||
];
|
||||
|
||||
$: entries = links.filter((link) => hasPrivileges(user.privileges || [], link.privileges));
|
||||
</script>
|
||||
|
||||
<aside class="admin-sidebar">
|
||||
<nav>
|
||||
<ul>
|
||||
{#each entries as link}
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
class="sidebar-link{$page.url.pathname.startsWith(link.href) ? ' active' : ''}"
|
||||
>{link.title}</a
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.admin-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #dddddd;
|
||||
height: 100%;
|
||||
max-width: 240px;
|
||||
width: 100%;
|
||||
border-right: 2px solid #b4b4b4;
|
||||
|
||||
& > nav {
|
||||
margin-top: 16px;
|
||||
|
||||
& > ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
& > li > a {
|
||||
display: block;
|
||||
padding: 8px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-sidebar,
|
||||
.admin-sidebar :global(a) {
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
@ -1,8 +1,10 @@
|
||||
<section class="page-wrapper">
|
||||
<div class="page-inner">
|
||||
<slot />
|
||||
<main>
|
||||
<div class="page-wrapper">
|
||||
<div class="page-inner">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.page-wrapper {
|
||||
@ -20,4 +22,11 @@
|
||||
max-width: 1080px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div class="aside-wrapper">
|
||||
<main class="aside-wrapper">
|
||||
<div class="aside-inner">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.aside-wrapper {
|
||||
|
16
src/lib/i18n/en/admin.json
Normal file
16
src/lib/i18n/en/admin.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Admin",
|
||||
"menu": {
|
||||
"users": "Users",
|
||||
"oauth2": "OAuth2 clients",
|
||||
"audit": "Audit logs"
|
||||
},
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"uuid": "UUID",
|
||||
"email": "Email",
|
||||
"privileges": "Privileges",
|
||||
"activated": "Activated",
|
||||
"registered": "Registered"
|
||||
}
|
||||
}
|
@ -6,5 +6,12 @@
|
||||
"manage": "Manage",
|
||||
"back": "Go back",
|
||||
"home": "Home page",
|
||||
"required": "Required fields"
|
||||
"required": "Required fields",
|
||||
"page": "Page",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"bool": {
|
||||
"true": "Yes",
|
||||
"false": "No"
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,12 @@ const config: Config<Params> = {
|
||||
locale: 'en',
|
||||
key: 'oauth2',
|
||||
loader: async () => await import('./en/oauth2.json')
|
||||
},
|
||||
{
|
||||
locale: 'en',
|
||||
key: 'admin',
|
||||
routes: [/\/ssoadmin/],
|
||||
loader: async () => await import('./en/admin.json')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
11
src/lib/server/admin-utils.ts
Normal file
11
src/lib/server/admin-utils.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { UserSession } from './users';
|
||||
import { hasPrivileges, type RequiredPrivileges } from '$lib/utils';
|
||||
|
||||
export class AdminUtils {
|
||||
static checkPrivileges(session: UserSession, privileges: RequiredPrivileges): void {
|
||||
if (!session.privileges?.length || !hasPrivileges(session.privileges, privileges)) {
|
||||
error(403, 'Forbidden resource');
|
||||
}
|
||||
}
|
||||
}
|
@ -71,17 +71,33 @@ export const oauth2Client = mysqlTable(
|
||||
export type OAuth2Client = typeof oauth2Client.$inferSelect;
|
||||
export type NewOAuth2Client = typeof oauth2Client.$inferInsert;
|
||||
|
||||
export const oauth2ClientManager = mysqlTable('o_auth2_client_manager', {
|
||||
id: int('id').autoincrement().notNull(),
|
||||
clientId: int('clientId')
|
||||
.references(() => oauth2Client.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
userId: int('userId')
|
||||
.references(() => user.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
issuerId: int('issuerId').references(() => user.id, { onDelete: 'set null' }),
|
||||
created_at: datetime('created_at', { mode: 'date', fsp: 6 })
|
||||
.default(sql`current_timestamp(6)`)
|
||||
.notNull(),
|
||||
updated_at: datetime('updated_at', { mode: 'date', fsp: 6 })
|
||||
.default(sql`current_timestamp(6)`)
|
||||
.notNull()
|
||||
});
|
||||
|
||||
export const oauth2ClientAuthorization = mysqlTable('o_auth2_client_authorization', {
|
||||
id: int('id').autoincrement().notNull(),
|
||||
scope: text('scope'),
|
||||
expires_at: timestamp('expires_at', { mode: 'date' })
|
||||
.default(sql`current_timestamp()`)
|
||||
.notNull(),
|
||||
expires_at: timestamp('expires_at', { mode: 'date' }),
|
||||
clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' }),
|
||||
userId: int('userId').references(() => user.id, { onDelete: 'cascade' }),
|
||||
created_at: datetime('created_at', { mode: 'date', fsp: 6 })
|
||||
.default(sql`current_timestamp(6)`)
|
||||
.notNull()
|
||||
.notNull(),
|
||||
current: tinyint('current').default(1).notNull()
|
||||
});
|
||||
|
||||
export type OAuth2ClientAuthorization = typeof oauth2ClientAuthorization.$inferSelect;
|
||||
@ -128,9 +144,12 @@ export type NewOAuth2Token = typeof oauth2Token.$inferInsert;
|
||||
|
||||
export const privilege = mysqlTable('privilege', {
|
||||
id: int('id').autoincrement().notNull(),
|
||||
name: text('name').notNull()
|
||||
name: text('name').notNull(),
|
||||
clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' })
|
||||
});
|
||||
|
||||
export type Privilege = typeof privilege.$inferSelect;
|
||||
|
||||
export const upload = mysqlTable('upload', {
|
||||
id: int('id').autoincrement().notNull(),
|
||||
original_name: varchar('original_name', { length: 255 }).notNull(),
|
||||
@ -271,7 +290,23 @@ export const oauth2ClientRelations = relations(oauth2Client, ({ one, many }) =>
|
||||
}),
|
||||
o_auth2_client_authorizations: many(oauth2ClientAuthorization),
|
||||
o_auth2_client_urls: many(oauth2ClientUrl),
|
||||
o_auth2_tokens: many(oauth2Token)
|
||||
o_auth2_tokens: many(oauth2Token),
|
||||
o_auth2_managers: many(oauth2ClientManager)
|
||||
}));
|
||||
|
||||
export const oauth2ClientManagerRelations = relations(oauth2ClientManager, ({ one }) => ({
|
||||
user: one(user, {
|
||||
fields: [oauth2ClientManager.userId],
|
||||
references: [user.id]
|
||||
}),
|
||||
issuer: one(user, {
|
||||
fields: [oauth2ClientManager.issuerId],
|
||||
references: [user.id]
|
||||
}),
|
||||
o_auth2_client: one(oauth2Client, {
|
||||
fields: [oauth2ClientManager.clientId],
|
||||
references: [oauth2Client.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const uploadRelations = relations(upload, ({ one, many }) => ({
|
||||
@ -329,8 +364,12 @@ export const userPrivilegesPrivilegeRelations = relations(userPrivilegesPrivileg
|
||||
})
|
||||
}));
|
||||
|
||||
export const privilegeRelations = relations(privilege, ({ many }) => ({
|
||||
user_privileges_privileges: many(userPrivilegesPrivilege)
|
||||
export const privilegeRelations = relations(privilege, ({ one, many }) => ({
|
||||
user_privileges_privileges: many(userPrivilegesPrivilege),
|
||||
o_auth2_client: one(oauth2Client, {
|
||||
fields: [privilege.clientId],
|
||||
references: [oauth2Client.id]
|
||||
})
|
||||
}));
|
||||
|
||||
export const userTokenRelations = relations(userToken, ({ one }) => ({
|
||||
|
@ -29,7 +29,11 @@ export class OAuth2Users {
|
||||
.from(oauth2ClientAuthorization)
|
||||
.innerJoin(oauth2Client, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
||||
.where(
|
||||
and(eq(oauth2Client.client_id, clientId), eq(oauth2ClientAuthorization.userId, userId))
|
||||
and(
|
||||
eq(oauth2Client.client_id, clientId),
|
||||
eq(oauth2ClientAuthorization.userId, userId),
|
||||
eq(oauth2ClientAuthorization.current, 1)
|
||||
)
|
||||
)
|
||||
).filter(({ scope }) => {
|
||||
const splitScope = OAuth2Clients.splitScope(scope || '');
|
||||
@ -60,7 +64,7 @@ export class OAuth2Users {
|
||||
|
||||
await db
|
||||
.update(oauth2ClientAuthorization)
|
||||
.set({ scope: OAuth2Clients.joinScope(splitScope) })
|
||||
.set({ scope: OAuth2Clients.joinScope(splitScope), current: 1, expires_at: null })
|
||||
.where(eq(oauth2ClientAuthorization.id, existing.id));
|
||||
return;
|
||||
}
|
||||
@ -78,11 +82,13 @@ export class OAuth2Users {
|
||||
|
||||
await OAuth2Tokens.wipeClientTokens(client, subject);
|
||||
await db
|
||||
.delete(oauth2ClientAuthorization)
|
||||
.update(oauth2ClientAuthorization)
|
||||
.set({ current: 0, expires_at: new Date() })
|
||||
.where(
|
||||
and(
|
||||
eq(oauth2ClientAuthorization.userId, subject.id),
|
||||
eq(oauth2ClientAuthorization.clientId, client.id)
|
||||
eq(oauth2ClientAuthorization.clientId, client.id),
|
||||
eq(oauth2ClientAuthorization.current, 1)
|
||||
)
|
||||
);
|
||||
|
||||
@ -95,7 +101,12 @@ export class OAuth2Users {
|
||||
.from(oauth2Client)
|
||||
.innerJoin(oauth2ClientAuthorization, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
||||
.leftJoin(oauth2ClientUrl, eq(oauth2ClientUrl.clientId, oauth2Client.id))
|
||||
.where(and(eq(oauth2ClientAuthorization.userId, subject.id)));
|
||||
.where(
|
||||
and(
|
||||
eq(oauth2ClientAuthorization.userId, subject.id),
|
||||
eq(oauth2ClientAuthorization.current, 1)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static async issueIdToken(subject: User, client: OAuth2Client, scope: string[], nonce?: string) {
|
||||
|
@ -62,6 +62,6 @@ export class Uploads {
|
||||
file: newName,
|
||||
uploaderId: subject.id
|
||||
});
|
||||
await db.update(user).set({ pictureId: retval.insertId });
|
||||
await db.update(user).set({ pictureId: retval.insertId }).where(eq(user.id, subject.id));
|
||||
}
|
||||
}
|
||||
|
79
src/lib/server/users/admin.ts
Normal file
79
src/lib/server/users/admin.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { count, eq, ilike, like, or } from 'drizzle-orm';
|
||||
import {
|
||||
db,
|
||||
privilege,
|
||||
user,
|
||||
userPrivilegesPrivilege,
|
||||
type Privilege,
|
||||
type User
|
||||
} from '../drizzle';
|
||||
import type { Paginated, PaginationMeta } from '$lib/types';
|
||||
|
||||
export interface AdminUserListItem extends Omit<User, 'password'> {
|
||||
privileges: Privilege[];
|
||||
}
|
||||
|
||||
export class UsersAdmin {
|
||||
static async getAllUsers({
|
||||
filter,
|
||||
offset = 0,
|
||||
limit = 20
|
||||
}: {
|
||||
filter?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
const subfilter = `%${filter}%`;
|
||||
const searchExpression = filter
|
||||
? or(
|
||||
like(user.uuid, subfilter),
|
||||
ilike(user.username, subfilter),
|
||||
ilike(user.display_name, subfilter),
|
||||
ilike(user.email, subfilter)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const [{ rowCount }] = await db
|
||||
.select({ rowCount: count(user.id).mapWith(Number) })
|
||||
.from(user)
|
||||
.where(searchExpression);
|
||||
|
||||
const junkList = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.leftJoin(userPrivilegesPrivilege, eq(userPrivilegesPrivilege.userId, user.id))
|
||||
.leftJoin(privilege, eq(userPrivilegesPrivilege.privilegeId, privilege.id))
|
||||
.where(searchExpression)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
const meta: PaginationMeta = {
|
||||
rowCount,
|
||||
pageSize: limit,
|
||||
pageCount: Math.ceil(rowCount / limit)
|
||||
};
|
||||
|
||||
const list = junkList.reduce<AdminUserListItem[]>((accum, dbe) => {
|
||||
let user = accum.find((entry) => entry.id === dbe.user.id);
|
||||
if (!user) {
|
||||
user = { ...dbe.user, password: undefined, privileges: [] } as AdminUserListItem;
|
||||
accum.push(user);
|
||||
}
|
||||
|
||||
if (dbe.user_privileges_privilege && dbe.privilege) {
|
||||
if (
|
||||
!user.privileges.some((priv) => priv.id === dbe.user_privileges_privilege?.privilegeId)
|
||||
) {
|
||||
user.privileges.push(dbe.privilege);
|
||||
}
|
||||
}
|
||||
|
||||
return accum;
|
||||
}, []);
|
||||
|
||||
return <Paginated<AdminUserListItem>>{
|
||||
list,
|
||||
meta
|
||||
};
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { and, eq, or, sql } from 'drizzle-orm';
|
||||
import { db, user, type User } from '../drizzle';
|
||||
import { db, privilege, user, userPrivilegesPrivilege, type User } from '../drizzle';
|
||||
import type { UserSession } from './types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { CryptoUtils } from '../crypto-utils';
|
||||
@ -214,6 +214,21 @@ export class Users {
|
||||
}
|
||||
}
|
||||
|
||||
static async getUserPrivileges(subject: User) {
|
||||
const list = await db
|
||||
.select({
|
||||
privilege: privilege.name
|
||||
})
|
||||
.from(privilege)
|
||||
.innerJoin(userPrivilegesPrivilege, eq(privilege.id, userPrivilegesPrivilege.privilegeId))
|
||||
.where(eq(userPrivilegesPrivilege.userId, subject.id));
|
||||
|
||||
return list.reduce<string[]>(
|
||||
(accum, { privilege }) => (!accum.includes(privilege) ? [...accum, privilege] : accum),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
static anonymizeEmail(email: string) {
|
||||
const [name, domain] = email.split('@');
|
||||
const namePart = `${name.charAt(0)}${''.padStart(name.length - 2, '*')}${name.charAt(name.length - 1)}`;
|
||||
|
@ -3,4 +3,5 @@ export interface UserSession {
|
||||
uuid: string;
|
||||
name: string;
|
||||
username: string;
|
||||
privileges?: string[];
|
||||
}
|
||||
|
18
src/lib/types.ts
Normal file
18
src/lib/types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface UserSession {
|
||||
uid: number;
|
||||
uuid: string;
|
||||
name: string;
|
||||
username: string;
|
||||
privileges?: string[];
|
||||
}
|
||||
|
||||
export interface PaginationMeta {
|
||||
rowCount: number;
|
||||
pageSize: number;
|
||||
pageCount: number;
|
||||
}
|
||||
|
||||
export interface Paginated<T> {
|
||||
list: T[];
|
||||
meta: PaginationMeta;
|
||||
}
|
9
src/lib/utils.ts
Normal file
9
src/lib/utils.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export type RequiredPrivileges = (string | string[])[];
|
||||
|
||||
export const hasPrivileges = (list: string[], privileges: RequiredPrivileges) =>
|
||||
privileges.every((item) => {
|
||||
if (Array.isArray(item)) {
|
||||
return item.some((sub) => list.includes(sub));
|
||||
}
|
||||
return list.includes(item);
|
||||
});
|
@ -1,16 +1,5 @@
|
||||
<script lang="ts">
|
||||
import '../app.css'
|
||||
import '../app.css';
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<slot></slot>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
|
@ -1 +0,0 @@
|
||||
<slot />
|
24
src/routes/ssoadmin/+layout.server.ts
Normal file
24
src/routes/ssoadmin/+layout.server.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Users } from '$lib/server/users/index.js';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load = async ({ url, locals }) => {
|
||||
const userInfo = locals.session.data?.user;
|
||||
const currentUser = await Users.getBySession(userInfo);
|
||||
if (!userInfo || !currentUser) {
|
||||
await locals.session.destroy();
|
||||
return redirect(301, `/login?redirectTo=${encodeURIComponent(url.pathname)}`);
|
||||
}
|
||||
|
||||
// Only users with 'admin' privilege can access
|
||||
const privileges = await Users.getUserPrivileges(currentUser);
|
||||
if (!privileges.includes('admin')) {
|
||||
return error(404, 'Not Found');
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
...userInfo,
|
||||
privileges
|
||||
}
|
||||
};
|
||||
};
|
42
src/routes/ssoadmin/+layout.svelte
Normal file
42
src/routes/ssoadmin/+layout.svelte
Normal file
@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import AdminHeader from '$lib/components/admin/AdminHeader.svelte';
|
||||
import AdminSidebar from '$lib/components/admin/AdminSidebar.svelte';
|
||||
import type { PageData } from './$types';
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="admin-wrapper">
|
||||
<AdminHeader user={data.user} />
|
||||
|
||||
<div class="sidebar-wrapper">
|
||||
<AdminSidebar user={data.user} />
|
||||
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.admin-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
|
||||
& > main {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
background-color: #ececec;
|
||||
color: #000;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
3
src/routes/ssoadmin/+page.server.ts
Normal file
3
src/routes/ssoadmin/+page.server.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const load = async () => {
|
||||
return {};
|
||||
};
|
0
src/routes/ssoadmin/+page.svelte
Normal file
0
src/routes/ssoadmin/+page.svelte
Normal file
28
src/routes/ssoadmin/users/+page.server.ts
Normal file
28
src/routes/ssoadmin/users/+page.server.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { AdminUtils } from '$lib/server/admin-utils.js';
|
||||
import { UsersAdmin } from '$lib/server/users/admin.js';
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
export const load = async ({ parent, url }) => {
|
||||
const { user } = await parent();
|
||||
AdminUtils.checkPrivileges(user, ['admin:user']);
|
||||
|
||||
let limit = PAGE_SIZE;
|
||||
let page = 1;
|
||||
let filter: string | undefined = undefined;
|
||||
if (url.searchParams.has('page')) {
|
||||
page = Number(url.searchParams.get('page')) || 1;
|
||||
}
|
||||
|
||||
if (url.searchParams.has('pageSize')) {
|
||||
limit = Number(url.searchParams.get('pageSize')) || PAGE_SIZE;
|
||||
}
|
||||
|
||||
if (url.searchParams.has('filter')) {
|
||||
filter = url.searchParams.get('filter') as string;
|
||||
}
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
return await UsersAdmin.getAllUsers({ filter, limit, offset });
|
||||
};
|
102
src/routes/ssoadmin/users/+page.svelte
Normal file
102
src/routes/ssoadmin/users/+page.svelte
Normal file
@ -0,0 +1,102 @@
|
||||
<script lang="ts">
|
||||
import Paginator from '$lib/components/Paginator.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat('en-GB', { dateStyle: 'short', timeStyle: 'medium' });
|
||||
const formatDate = dateFormat.format.bind(null);
|
||||
</script>
|
||||
|
||||
<h1>{$t('admin.users.title')}</h1>
|
||||
|
||||
<div class="user-list">
|
||||
<Paginator meta={data.meta} />
|
||||
{#each data.list as user}
|
||||
<a href={`users/${user.uuid}`} class="user-wrapper">
|
||||
<div class="user">
|
||||
<div class="user-avatar-wrapper">
|
||||
<img class="user-avatar" src={`/api/avatar/${user.uuid}`} alt={user.display_name} />
|
||||
</div>
|
||||
|
||||
<div class="user-info">
|
||||
<h2 class="user-name">{user.display_name}</h2>
|
||||
<span class="user-username">@{user.username}</span>
|
||||
|
||||
<dl>
|
||||
<dt>{$t('admin.users.uuid')}</dt>
|
||||
<dd>{user.uuid}</dd>
|
||||
|
||||
<dt>{$t('admin.users.email')}</dt>
|
||||
<dd>{user.email}</dd>
|
||||
|
||||
{#if user.privileges.length}
|
||||
<dt>{$t('admin.users.privileges')}</dt>
|
||||
<dd>{user.privileges.map(({ name }) => name).join(', ')}</dd>
|
||||
{/if}
|
||||
|
||||
<dt>{$t('admin.users.activated')}</dt>
|
||||
<dd>{$t(`common.bool.${Boolean(user.activated)}`)}</dd>
|
||||
|
||||
<dt>{$t('admin.users.registered')}</dt>
|
||||
<dd>{formatDate(user.created_at)}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
<Paginator meta={data.meta} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.user-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.user-wrapper {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
transition: transform 100ms linear;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.01);
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 8px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.25);
|
||||
flex-grow: 1;
|
||||
|
||||
& .user-avatar {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
& .user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > dl {
|
||||
margin: 0;
|
||||
& > dt {
|
||||
font-weight: 600;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .user-name {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user