admin stuff
This commit is contained in:
parent
3bf4c7ce04
commit
1f5de32f61
@ -19,3 +19,4 @@ EMAIL_SMTP_PASS=
|
|||||||
REGISTRATIONS=true
|
REGISTRATIONS=true
|
||||||
ADDRESS_HEADER=X-Forwarded-For
|
ADDRESS_HEADER=X-Forwarded-For
|
||||||
XFF_DEPTH=1
|
XFF_DEPTH=1
|
||||||
|
AUTO_MIGRATE=true
|
||||||
|
@ -3,7 +3,7 @@ import { defineConfig } from 'drizzle-kit';
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: 'mysql',
|
dialect: 'mysql',
|
||||||
schema: './src/lib/server/drizzle/schema.ts',
|
schema: './src/lib/server/drizzle/schema.ts',
|
||||||
out: './src/lib/server/drizzle/migrations',
|
out: './migrations',
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
host: process.env.DATABASE_HOST as string,
|
host: process.env.DATABASE_HOST as string,
|
||||||
port: Number(process.env.DATABASE_PORT) || 3306,
|
port: Number(process.env.DATABASE_PORT) || 3306,
|
||||||
@ -13,4 +13,4 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
verbose: true,
|
verbose: true,
|
||||||
strict: 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` (
|
CREATE TABLE `audit_log` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`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)',
|
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||||
`actorId` int(11) DEFAULT 'NULL'
|
`actorId` int(11) DEFAULT 'NULL'
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `document` (
|
CREATE TABLE `document` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`title` text NOT NULL,
|
`title` text NOT NULL,
|
||||||
@ -21,7 +19,7 @@ CREATE TABLE `document` (
|
|||||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||||
`updated_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` (
|
CREATE TABLE `o_auth2_client` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`client_id` varchar(36) 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)',
|
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||||
CONSTRAINT `IDX_e9d16c213910ad57bd05e97b42` UNIQUE(`client_id`)
|
CONSTRAINT `IDX_e9d16c213910ad57bd05e97b42` UNIQUE(`client_id`)
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `o_auth2_client_authorization` (
|
CREATE TABLE `o_auth2_client_authorization` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`scope` text DEFAULT 'NULL',
|
`scope` text DEFAULT 'NULL',
|
||||||
@ -47,7 +45,7 @@ CREATE TABLE `o_auth2_client_authorization` (
|
|||||||
`userId` int(11) DEFAULT 'NULL',
|
`userId` int(11) DEFAULT 'NULL',
|
||||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `o_auth2_client_url` (
|
CREATE TABLE `o_auth2_client_url` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`url` varchar(255) 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)',
|
`updated_at` timestamp(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||||
`clientId` int(11) DEFAULT 'NULL'
|
`clientId` int(11) DEFAULT 'NULL'
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `o_auth2_token` (
|
CREATE TABLE `o_auth2_token` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`type` enum('code','access_token','refresh_token') 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)',
|
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||||
`pcke` text DEFAULT 'NULL'
|
`pcke` text DEFAULT 'NULL'
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `privilege` (
|
CREATE TABLE `privilege` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`name` text NOT NULL
|
`name` text NOT NULL
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `upload` (
|
CREATE TABLE `upload` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`original_name` varchar(255) 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)',
|
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)',
|
||||||
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
`updated_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `user` (
|
CREATE TABLE `user` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`uuid` varchar(36) NOT NULL,
|
`uuid` varchar(36) NOT NULL,
|
||||||
@ -102,12 +100,12 @@ CREATE TABLE `user` (
|
|||||||
CONSTRAINT `IDX_78a916df40e02a9deb1c4b75ed` UNIQUE(`username`),
|
CONSTRAINT `IDX_78a916df40e02a9deb1c4b75ed` UNIQUE(`username`),
|
||||||
CONSTRAINT `IDX_e12875dfb3b1d92d7d7c5377e2` UNIQUE(`email`)
|
CONSTRAINT `IDX_e12875dfb3b1d92d7d7c5377e2` UNIQUE(`email`)
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `user_privileges_privilege` (
|
CREATE TABLE `user_privileges_privilege` (
|
||||||
`userId` int(11) NOT NULL,
|
`userId` int(11) NOT NULL,
|
||||||
`privilegeId` int(11) NOT NULL
|
`privilegeId` int(11) NOT NULL
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `user_token` (
|
CREATE TABLE `user_token` (
|
||||||
`id` int(11) AUTO_INCREMENT NOT NULL,
|
`id` int(11) AUTO_INCREMENT NOT NULL,
|
||||||
`token` text NOT NULL,
|
`token` text NOT NULL,
|
||||||
@ -117,21 +115,21 @@ CREATE TABLE `user_token` (
|
|||||||
`nonce` text DEFAULT 'NULL',
|
`nonce` text DEFAULT 'NULL',
|
||||||
`created_at` datetime(6) NOT NULL DEFAULT 'current_timestamp(6)'
|
`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 `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;--> statement-breakpoint
|
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;--> 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;
|
||||||
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` 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;--> 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;
|
||||||
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_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;--> 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;
|
||||||
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_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;--> statement-breakpoint
|
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;--> statement-breakpoint
|
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;--> statement-breakpoint
|
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;--> statement-breakpoint
|
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;--> statement-breakpoint
|
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;--> statement-breakpoint
|
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`);--> statement-breakpoint
|
CREATE INDEX `IDX_0664a7ff494a1859a09014c0f1` ON `user_privileges_privilege` (`userId`);
|
||||||
CREATE INDEX `IDX_e71171f4ed20bc8564a1819d0b` ON `user_privileges_privilege` (`privilegeId`);
|
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,
|
"when": 1715871691221,
|
||||||
"tag": "0000_initial",
|
"tag": "0000_initial",
|
||||||
"breakpoints": true
|
"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 {
|
interface Locals {
|
||||||
session: Session<SessionData>;
|
session: Session<SessionData>;
|
||||||
user: User;
|
user: User;
|
||||||
|
privileges: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PageData {
|
interface PageData {
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { SESSION_SECRET } from '$env/static/private';
|
import { AUTO_MIGRATE, SESSION_SECRET } from '$env/static/private';
|
||||||
import '$lib/server/drizzle';
|
import { db } from '$lib/server/drizzle';
|
||||||
|
import { migrate } from 'drizzle-orm/mysql2/migrator';
|
||||||
import { handleSession } from 'svelte-kit-cookie-session';
|
import { handleSession } from 'svelte-kit-cookie-session';
|
||||||
|
|
||||||
|
if (AUTO_MIGRATE === 'true') {
|
||||||
|
await migrate(db, { migrationsFolder: './migrations' });
|
||||||
|
}
|
||||||
|
|
||||||
export const handle = handleSession({
|
export const handle = handleSession({
|
||||||
secret: SESSION_SECRET
|
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">
|
<main>
|
||||||
|
<div class="page-wrapper">
|
||||||
<div class="page-inner">
|
<div class="page-inner">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.page-wrapper {
|
.page-wrapper {
|
||||||
@ -20,4 +22,11 @@
|
|||||||
max-width: 1080px;
|
max-width: 1080px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
min-height: 100vh;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<div class="aside-wrapper">
|
<main class="aside-wrapper">
|
||||||
<div class="aside-inner">
|
<div class="aside-inner">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.aside-wrapper {
|
.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",
|
"manage": "Manage",
|
||||||
"back": "Go back",
|
"back": "Go back",
|
||||||
"home": "Home page",
|
"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',
|
locale: 'en',
|
||||||
key: 'oauth2',
|
key: 'oauth2',
|
||||||
loader: async () => await import('./en/oauth2.json')
|
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 OAuth2Client = typeof oauth2Client.$inferSelect;
|
||||||
export type NewOAuth2Client = typeof oauth2Client.$inferInsert;
|
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', {
|
export const oauth2ClientAuthorization = mysqlTable('o_auth2_client_authorization', {
|
||||||
id: int('id').autoincrement().notNull(),
|
id: int('id').autoincrement().notNull(),
|
||||||
scope: text('scope'),
|
scope: text('scope'),
|
||||||
expires_at: timestamp('expires_at', { mode: 'date' })
|
expires_at: timestamp('expires_at', { mode: 'date' }),
|
||||||
.default(sql`current_timestamp()`)
|
|
||||||
.notNull(),
|
|
||||||
clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' }),
|
clientId: int('clientId').references(() => oauth2Client.id, { onDelete: 'cascade' }),
|
||||||
userId: int('userId').references(() => user.id, { onDelete: 'cascade' }),
|
userId: int('userId').references(() => user.id, { onDelete: 'cascade' }),
|
||||||
created_at: datetime('created_at', { mode: 'date', fsp: 6 })
|
created_at: datetime('created_at', { mode: 'date', fsp: 6 })
|
||||||
.default(sql`current_timestamp(6)`)
|
.default(sql`current_timestamp(6)`)
|
||||||
.notNull()
|
.notNull(),
|
||||||
|
current: tinyint('current').default(1).notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type OAuth2ClientAuthorization = typeof oauth2ClientAuthorization.$inferSelect;
|
export type OAuth2ClientAuthorization = typeof oauth2ClientAuthorization.$inferSelect;
|
||||||
@ -128,9 +144,12 @@ export type NewOAuth2Token = typeof oauth2Token.$inferInsert;
|
|||||||
|
|
||||||
export const privilege = mysqlTable('privilege', {
|
export const privilege = mysqlTable('privilege', {
|
||||||
id: int('id').autoincrement().notNull(),
|
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', {
|
export const upload = mysqlTable('upload', {
|
||||||
id: int('id').autoincrement().notNull(),
|
id: int('id').autoincrement().notNull(),
|
||||||
original_name: varchar('original_name', { length: 255 }).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_authorizations: many(oauth2ClientAuthorization),
|
||||||
o_auth2_client_urls: many(oauth2ClientUrl),
|
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 }) => ({
|
export const uploadRelations = relations(upload, ({ one, many }) => ({
|
||||||
@ -329,8 +364,12 @@ export const userPrivilegesPrivilegeRelations = relations(userPrivilegesPrivileg
|
|||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const privilegeRelations = relations(privilege, ({ many }) => ({
|
export const privilegeRelations = relations(privilege, ({ one, many }) => ({
|
||||||
user_privileges_privileges: many(userPrivilegesPrivilege)
|
user_privileges_privileges: many(userPrivilegesPrivilege),
|
||||||
|
o_auth2_client: one(oauth2Client, {
|
||||||
|
fields: [privilege.clientId],
|
||||||
|
references: [oauth2Client.id]
|
||||||
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const userTokenRelations = relations(userToken, ({ one }) => ({
|
export const userTokenRelations = relations(userToken, ({ one }) => ({
|
||||||
|
@ -29,7 +29,11 @@ export class OAuth2Users {
|
|||||||
.from(oauth2ClientAuthorization)
|
.from(oauth2ClientAuthorization)
|
||||||
.innerJoin(oauth2Client, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
.innerJoin(oauth2Client, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
||||||
.where(
|
.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 }) => {
|
).filter(({ scope }) => {
|
||||||
const splitScope = OAuth2Clients.splitScope(scope || '');
|
const splitScope = OAuth2Clients.splitScope(scope || '');
|
||||||
@ -60,7 +64,7 @@ export class OAuth2Users {
|
|||||||
|
|
||||||
await db
|
await db
|
||||||
.update(oauth2ClientAuthorization)
|
.update(oauth2ClientAuthorization)
|
||||||
.set({ scope: OAuth2Clients.joinScope(splitScope) })
|
.set({ scope: OAuth2Clients.joinScope(splitScope), current: 1, expires_at: null })
|
||||||
.where(eq(oauth2ClientAuthorization.id, existing.id));
|
.where(eq(oauth2ClientAuthorization.id, existing.id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -78,11 +82,13 @@ export class OAuth2Users {
|
|||||||
|
|
||||||
await OAuth2Tokens.wipeClientTokens(client, subject);
|
await OAuth2Tokens.wipeClientTokens(client, subject);
|
||||||
await db
|
await db
|
||||||
.delete(oauth2ClientAuthorization)
|
.update(oauth2ClientAuthorization)
|
||||||
|
.set({ current: 0, expires_at: new Date() })
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(oauth2ClientAuthorization.userId, subject.id),
|
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)
|
.from(oauth2Client)
|
||||||
.innerJoin(oauth2ClientAuthorization, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
.innerJoin(oauth2ClientAuthorization, eq(oauth2ClientAuthorization.clientId, oauth2Client.id))
|
||||||
.leftJoin(oauth2ClientUrl, eq(oauth2ClientUrl.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) {
|
static async issueIdToken(subject: User, client: OAuth2Client, scope: string[], nonce?: string) {
|
||||||
|
@ -62,6 +62,6 @@ export class Uploads {
|
|||||||
file: newName,
|
file: newName,
|
||||||
uploaderId: subject.id
|
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 bcrypt from 'bcryptjs';
|
||||||
import { and, eq, or, sql } from 'drizzle-orm';
|
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 type { UserSession } from './types';
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import { CryptoUtils } from '../crypto-utils';
|
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) {
|
static anonymizeEmail(email: string) {
|
||||||
const [name, domain] = email.split('@');
|
const [name, domain] = email.split('@');
|
||||||
const namePart = `${name.charAt(0)}${''.padStart(name.length - 2, '*')}${name.charAt(name.length - 1)}`;
|
const namePart = `${name.charAt(0)}${''.padStart(name.length - 2, '*')}${name.charAt(name.length - 1)}`;
|
||||||
|
@ -3,4 +3,5 @@ export interface UserSession {
|
|||||||
uuid: string;
|
uuid: string;
|
||||||
name: string;
|
name: string;
|
||||||
username: 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">
|
<script lang="ts">
|
||||||
import '../app.css'
|
import '../app.css';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<slot></slot>
|
||||||
<slot></slot>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
min-height: 100vh;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -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