Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
55 changes: 55 additions & 0 deletions __tests__/user_schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, it, expect } from 'vitest';
import { UserSchema, UserRegistrationSchema, SavedJobSchema } from '../types/user';

describe('User Authentication Schema', () => {
it('should validate a complete user object', () => {
const validUser = {
id: '123e4567-e89b-12d3-a456-426614174000',
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
profilePictureUrl: 'https://example.com/profile.jpg',
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
lastLogin: new Date(),
};

const result = UserSchema.safeParse(validUser);
expect(result.success).toBe(true);
});

it('should validate user registration', () => {
const validRegistration = {
email: '[email protected]',
password: 'securePassword123!',
firstName: 'John',
lastName: 'Doe',
};

const result = UserRegistrationSchema.safeParse(validRegistration);
expect(result.success).toBe(true);
});

it('should reject invalid email', () => {
const invalidUser = {
email: 'invalid-email',
password: 'short',
};

const result = UserRegistrationSchema.safeParse(invalidUser);
expect(result.success).toBe(false);
});

it('should validate saved job', () => {
const validSavedJob = {
id: '123e4567-e89b-12d3-a456-426614174000',
userId: '123e4567-e89b-12d3-a456-426614174001',
jobId: 'job123',
savedAt: new Date(),
};

const result = SavedJobSchema.safeParse(validSavedJob);
expect(result.success).toBe(true);
});
});
42 changes: 42 additions & 0 deletions db/migrations/001_create_users_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- Create users table for authentication
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
profile_picture_url TEXT,
email_verified BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP WITH TIME ZONE
);

-- Create index for faster email lookup
CREATE INDEX idx_users_email ON users(email);

-- Create saved_jobs table to link with users
CREATE TABLE saved_jobs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
job_id VARCHAR(255) NOT NULL,
saved_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, job_id)
);

-- Function to automatically update updated_at timestamp
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';

-- Trigger to automatically update updated_at for users table
CREATE TRIGGER update_users_modtime
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_modified_column();
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"tailwindcss": "3.4.1",
"tailwindcss-animate": "^1.0.6",
"uuid": "^9.0.1",
"zod": "^3.21.4"
"zod": "^3.25.16"
},
"devDependencies": {
"@types/uuid": "^9.0.8",
Expand Down
32 changes: 32 additions & 0 deletions types/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { z } from 'zod';

// User schema for validation
export const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
firstName: z.string().optional(),
lastName: z.string().optional(),
profilePictureUrl: z.string().url().optional(),
emailVerified: z.boolean().default(false),
createdAt: z.date(),
updatedAt: z.date(),
lastLogin: z.date().optional(),
});

export const UserRegistrationSchema = z.object({
email: z.string().email(),
password: z.string().min(8, 'Password must be at least 8 characters'),
firstName: z.string().optional(),
lastName: z.string().optional(),
});

export const SavedJobSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
jobId: z.string(),
savedAt: z.date(),
});

export type User = z.infer<typeof UserSchema>;
export type UserRegistration = z.infer<typeof UserRegistrationSchema>;
export type SavedJob = z.infer<typeof SavedJobSchema>;