Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/user/drizzle/0001_add_admin_user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "users" ADD COLUMN "is_admin" boolean DEFAULT false;
143 changes: 143 additions & 0 deletions backend/user/drizzle/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"id": "5293f5bb-f4d5-43a4-b2bf-f6ebb241dde1",
"prevId": "f01e3d91-1038-48b8-a073-a6a9a8a308fd",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.admin": {
"name": "admin",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"action": {
"name": "action",
"type": "action",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"first_name": {
"name": "first_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"last_name": {
"name": "last_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"failed_attempts": {
"name": "failed_attempts",
"type": "smallint",
"primaryKey": false,
"notNull": false,
"default": 0
},
"unlock_time": {
"name": "unlock_time",
"type": "timestamp (6) with time zone",
"primaryKey": false,
"notNull": false
},
"attempted_questions": {
"name": "attempted_questions",
"type": "integer[]",
"primaryKey": false,
"notNull": false
},
"is_admin": {
"name": "is_admin",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
},
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
}
}
}
},
"enums": {
"public.action": {
"name": "action",
"schema": "public",
"values": [
"SEED"
]
}
},
"schemas": {},
"sequences": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
7 changes: 7 additions & 0 deletions backend/user/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"when": 1728143079049,
"tag": "0000_initial_schema",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1730968227580,
"tag": "0001_add_admin_user",
"breakpoints": true
}
]
}
4 changes: 3 additions & 1 deletion backend/user/src/controllers/auth-check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const checkIsAuthed: IRouteHandler = async (req, res) => {
'[/auth-check/check-is-authed]: Expires At ' + new Date(expireTimeInMillis).toLocaleString()
);
const user = await db
.select({ name: users.username })
.select({ name: users.username, isAdmin: users.isAdmin, email: users.email })
.from(users)
.where(eq(users.id, decoded.id))
.limit(1);
Expand All @@ -25,6 +25,8 @@ export const checkIsAuthed: IRouteHandler = async (req, res) => {
expiresAt: expireTimeInMillis,
userId: decoded.id,
username: user.length > 0 ? user[0].name : undefined,
email: user.length > 0 ? user[0].email : undefined,
isAdmin: user.length > 0 ? user[0].isAdmin : undefined,
});
}

Expand Down
12 changes: 11 additions & 1 deletion backend/user/src/lib/db/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { integer, pgEnum, pgTable, smallint, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
import {
boolean,
integer,
pgEnum,
pgTable,
smallint,
timestamp,
uuid,
varchar,
} from 'drizzle-orm/pg-core';

// Define the user table
export const users = pgTable('users', {
Expand All @@ -11,6 +20,7 @@ export const users = pgTable('users', {
failedAttempts: smallint('failed_attempts').default(0), // Failed counts
unlockTime: timestamp('unlock_time', { precision: 6, withTimezone: true }), // If failed counts > limit, block all attempts until this time.
attemptedQuestions: integer('attempted_questions').array(),
isAdmin: boolean('is_admin').default(false),
});

export const actionEnum = pgEnum('action', ['SEED']);
Expand Down
12 changes: 10 additions & 2 deletions backend/user/src/lib/db/seed.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { eq } from 'drizzle-orm';
import { eq, InferInsertModel } from 'drizzle-orm';

import { generatePasswordHash } from '@/lib/passwords';

import { admin as adminTable, db, users as usersTable } from '.';

const TEST_USER_CREDENTIALS = [
const TEST_USER_CREDENTIALS: Array<InferInsertModel<typeof usersTable>> = [
{
username: 'testuser01',
email: '[email protected]',
Expand All @@ -19,6 +19,14 @@ const TEST_USER_CREDENTIALS = [
lastName: 'user02',
password: '123456789', // For local testing purposes
},
{
username: 'adminuser01',
email: '[email protected]',
firstName: 'admin',
lastName: 'user01',
password: 'IamPeerprepAdmin!9',
isAdmin: true,
},
];

const main = async () => {
Expand Down
7 changes: 0 additions & 7 deletions frontend/.env.local

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,6 @@
"tailwindcss": "^3.4.11",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
"vite": "^5.4.10"
}
}
48 changes: 33 additions & 15 deletions frontend/src/components/blocks/authed/with-nav-blocker.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { FC, PropsWithChildren } from 'react';
import { useBlocker } from 'react-router-dom';

import { Button } from '@/components/ui/button';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogTitle,
} from '@/components/ui/dialog';

export const WithNavBlocker: FC<PropsWithChildren> = ({ children }) => {
const blocker = useBlocker(
Expand All @@ -13,21 +20,32 @@ export const WithNavBlocker: FC<PropsWithChildren> = ({ children }) => {
{blocker.state === 'blocked' && (
<Dialog modal open>
<DialogContent className='text-primary border-secondary-foreground/40 flex flex-col gap-8'>
<h1 className='text-lg font-medium'>
<DialogTitle className='text-primary text-lg'>
Are you sure you want to navigate away from this page?
</h1>
<div className='flex flex-row justify-between'>
<Button onClick={blocker.reset}>
<span>Cancel</span>
</Button>
<Button variant='destructive' onClick={blocker.proceed}>
<span>Leave Page</span>
</Button>
</div>
<div
id='blockDialogClose'
className='bg-background absolute right-4 top-4 z-50 size-4'
/>
</DialogTitle>
<VisuallyHidden>
<DialogDescription />
</VisuallyHidden>
<DialogFooter>
<div className='flex w-full flex-row justify-between'>
<Button onClick={blocker.reset}>
<span>Cancel</span>
</Button>
<Button
variant='destructive'
onClick={() => {
localStorage.removeItem('ai_chat_history');
blocker.proceed();
}}
>
<span>Leave Page</span>
</Button>
</div>
<div
id='blockDialogClose'
className='bg-background absolute right-4 top-4 z-50 size-4'
/>
</DialogFooter>
</DialogContent>
</Dialog>
)}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/blocks/interview/chat/chat-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ export const ChatLayout = ({
<Trash2 className='size-4' />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogContent className='border-border'>
<AlertDialogHeader>
<AlertDialogTitle>Clear Chat History</AlertDialogTitle>
<AlertDialogTitle className='text-primary'>Clear Chat History</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to clear the chat history? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel className='text-primary'>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onClearHistory}>Clear History</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,22 @@ export const MarkdownComponent = ({
code({ children, className, ...rest }) {
const [copyCodeText, setCopyCodeText] = useState('Copy Code');

const match = /language-(\w+)/.exec(className || '');

const onCopy = (code: string) => {
const language = match?.[1];

if (language) {
localStorage.setItem('ai-asst-lang', language);
}

navigator.clipboard.writeText(code);
setCopyCodeText('Copied!');
setTimeout(() => {
setCopyCodeText(defaultCopyCodeText);
}, 3000);
};

const match = /language-(\w+)/.exec(className || '');
return match ? (
<div className='flex flex-col'>
<div className='inline-flex translate-y-[10px] items-center justify-between rounded-t-sm bg-gray-700 p-1 text-sm'>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/blocks/interview/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ export const Editor = ({
height={`${Math.max((height as number) - EXTENSION_HEIGHT, MIN_EDITOR_HEIGHT)}px`}
value={code}
onChange={handleCodeChange}
onPaste={(_event) => {
const lang = localStorage.getItem('ai-asst-lang');

if (lang) {
setLanguage(lang as LanguageName);
localStorage.removeItem('ai-assist-lang');
}
}}
theme={themePreset}
lang={language}
basicSetup={{
Expand Down
Loading