好的, 我们可以添加雇主发布工作的功能和用户上传、管理个人简历的功能。这里是实现这些功能的步骤:
- 首先, 我们需要修改数据库结构。在 Turso 数据库中添加新的表:
-- 用户表
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('jobseeker', 'employer')),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 简历表
CREATE TABLE resumes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
file_name TEXT NOT NULL,
file_path TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 修改jobs表,添加employer_id
ALTER TABLE jobs ADD COLUMN employer_id INTEGER;
ALTER TABLE jobs ADD FOREIGN KEY (employer_id) REFERENCES users(id);- 修改
auth.ts文件, 在用户认证后创建或更新用户信息:
// auth.ts
// ... (保留之前的导入)
async function createOrUpdateUser(db, userInfo) {
const existingUser = await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [userInfo.email]
});
if (existingUser.rows.length === 0) {
await db.execute({
sql: 'INSERT INTO users (email, name, role) VALUES (?, ?, ?)',
args: [userInfo.email, userInfo.name, 'jobseeker']
});
} else {
await db.execute({
sql: 'UPDATE users SET name = ? WHERE email = ?',
args: [userInfo.name, userInfo.email]
});
}
return await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [userInfo.email]
});
}
export const auth = (app: Elysia) =>
app
// ... (保留之前的代码)
.get('/auth/google/callback', async ({ query, jwt, setCookie, set, db }) => {
// ... (保留之前的代码)
const userInfo = await oauth.getUserInfo(client, tokens);
const dbUser = await createOrUpdateUser(db, {
email: userInfo.email,
name: userInfo.name
});
const token = await jwt.sign({
id: dbUser.rows[0].id,
email: dbUser.rows[0].email,
name: dbUser.rows[0].name,
role: dbUser.rows[0].role
});
// ... (保留之前的代码)
})
.get('/auth/github/callback', async ({ query, jwt, setCookie, set, db }) => {
// ... (类似的修改)
})
// ... (保留之前的代码)- 在
src/index.ts中添加新的路由和功能:
import { Elysia } from 'elysia'
import { html } from '@elysiajs/html'
import { createClient } from '@libsql/client'
import { staticPlugin } from '@elysiajs/static'
import { auth } from './auth'
import 'unocss'
const db = createClient({
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN
})
const app = new Elysia()
.use(html())
.use(staticPlugin())
.use(auth)
.get('/', ({ html, user }) => html`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Board</title>
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
<script src="https://unpkg.com/unocss@0.51.8/dist/unocss.js"></script>
</head>
<body class="p-4" id="main-body">
<header class="flex justify-between items-center mb-4">
<h1 class="text-2xl font-bold">Job Board</h1>
${user
? `<div>
Welcome, ${user.name}!
<a href="/profile" class="ml-2 text-blue-500">Profile</a>
<a href="/auth/logout" class="ml-2 text-blue-500">Logout</a>
</div>`
: `<div>
<a href="/auth/google" class="text-blue-500 mr-2">Login with Google</a>
<a href="/auth/github" class="text-blue-500">Login with GitHub</a>
</div>`
}
</header>
${user
? `<div class="mb-4">
<label for="template-select">Choose a template:</label>
<select id="template-select" hx-get="/apply-template" hx-target="#main-body" hx-swap="outerHTML">
<option value="sunny">Sunny Sky</option>
<option value="night">Night Sky</option>
<option value="sunset">Red Sunset</option>
<option value="rainy">Gentle Rain</option>
<option value="snowy">Winter Snow</option>
</select>
<button hx-get="/edit-template" class="ml-2 bg-blue-500 text-white p-2 rounded">Edit Template</button>
</div>
<div id="job-list" hx-get="/jobs" hx-trigger="load"></div>
${user. Role === 'employer'
? `<form hx-post="/jobs" hx-target="#job-list" class="mt-4">
<input type="text" name="title" placeholder="Job Title" required class="border p-2 mb-2 w-full">
<input type="text" name="company" placeholder="Company" required class="border p-2 mb-2 w-full">
<textarea name="description" placeholder="Job Description" required class="border p-2 mb-2 w-full h-32"></textarea>
<button type="submit" class="bg-blue-500 text-white p-2 rounded">Post Job</button>
</form>`
: ''
}`
: `<p>Please login to view and post jobs.</p>`
}
</body>
</html>
`)
.get ('/jobs', async ({ user }) => {
if (!user) return 'Please login to view jobs.'
const jobs = await db.execute('SELECT jobs.*, users.name as employer_name FROM jobs JOIN users ON jobs.employer_id = users.id ORDER BY jobs.created_at DESC')
return jobs.rows.map(job => `
<div class="job bg-white shadow-md rounded p-4 mb-4">
<h2 class="text-xl font-bold">${job.title}</h2>
<h3 class="text-lg">${job.company}</h3>
<p class="text-sm text-gray-600">Posted by: ${job.employer_name}</p>
<p class="mt-2">${job.description}</p>
${user.role === 'jobseeker'
? `<button hx-post="/apply/${job.id}" class="mt-2 bg-green-500 text-white p-2 rounded">Apply</button>`
: ''
}
</div>
`).join('')
})
.post('/jobs', async ({ body, user }) => {
if (!user || user.role !== 'employer') return 'Only employers can post jobs.'
const { title, company, description } = body
await db.execute({
sql: 'INSERT INTO jobs (title, company, description, employer_id) VALUES (?, ?, ?, ?)',
args: [title, company, description, user.id]
})
return app.handle({ path: '/jobs', method: 'GET' })
})
.get('/profile', async ({ user, html }) => {
if (!user) return 'Please login to view your profile.'
const resumes = await db.execute({
sql: 'SELECT * FROM resumes WHERE user_id = ?',
args: [user.id]
})
return html`
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">Your Profile</h1>
<p>Name: ${user.name}</p>
<p>Email: ${user.email}</p>
<p>Role: ${user.role}</p>
${user.role === 'jobseeker'
? `<h2 class="text-xl font-bold mt-4 mb-2">Your Resumes</h2>
<ul>
${resumes.rows.map(resume => `
<li>${resume.file_name}
<button hx-delete="/resume/${resume.id}" hx-target="closest li" class="text-red-500">Delete</button>
</li>
`).join('')}
</ul>
<form hx-post="/resume" hx-encoding="multipart/form-data" hx-target="ul">
<input type="file" name="resume" accept=".pdf,.doc,.docx">
<button type="submit" class="bg-blue-500 text-white p-2 rounded mt-2">Upload Resume</button>
</form>`
: ''
}
${user.role === 'jobseeker'
? `<button hx-post="/switch-to-employer" hx-target="body" class="bg-yellow-500 text-white p-2 rounded mt-4">Switch to Employer</button>`
: `<button hx-post="/switch-to-jobseeker" hx-target="body" class="bg-green-500 text-white p-2 rounded mt-4">Switch to Job Seeker</button>`
}
</div>
`
})
.post ('/resume', async ({ user, request }) => {
if (!user || user.role !== 'jobseeker') return 'Only job seekers can upload resumes.'
const formData = await request.formData()
const file = formData.get('resume') as File
if (!file) return 'No file uploaded.'
// 在实际应用中,你需要处理文件上传,这里我们只是模拟
const fileName = file.name
const filePath = `/uploads/${Date.now()}-${fileName}`
Await db.Execute ({
sql: 'INSERT INTO resumes (user_id, file_name, file_path) VALUES (?, ?, ?)',
args: [user.id, fileName, filePath]
})
return `<li>${fileName} <button hx-delete="/resume/${fileName}" hx-target="closest li" class="text-red-500">Delete</button></li>`
})
.delete('/resume/:id', async ({ user, params }) => {
if (!user || user.role !== 'jobseeker') return 'Only job seekers can delete resumes.'
await db.execute({
sql: 'DELETE FROM resumes WHERE id = ? AND user_id = ?',
args: [params.id, user.id]
})
return ''
})
.post('/switch-to-employer', async ({ user }) => {
if (!user) return 'Please login to switch roles.'
await db.execute({
sql: 'UPDATE users SET role = "employer" WHERE id = ?',
args: [user.id]
})
return app.handle({ path: '/', method: 'GET' })
})
.post('/switch-to-jobseeker', async ({ user }) => {
if (!user) return 'Please login to switch roles.'
await db.execute({
sql: 'UPDATE users SET role = "jobseeker" WHERE id = ?',
args: [user.id]
})
return app.handle({ path: '/', method: 'GET' })
})
.post('/apply/:jobId', async ({ user, params }) => {
if (!user || user.role !== 'jobseeker') return 'Only job seekers can apply for jobs.'
// 在实际应用中,你需要处理职位申请逻辑
return `<p>You have successfully applied for job ${params.jobId}</p>`
})
// ... (保留之前的路由)
.listen(3000)
console.log(`Elysia is running at ${app.server?.hostname}:${app.server?.port}`)这个实现添加了以下功能:
- 用户角色 (求职者和雇主)
- 雇主可以发布工作
- 求职者可以上传和管理简历
- 用户可以在求职者和雇主角色之间切换
- 求职者可以申请工作
请注意, 这个实现仍然是基本的, 你可能需要根据你的具体需求进行进一步的改进, 例如:
- 实现更复杂的文件上传和存储逻辑
- 添加职位申请的详细流程和跟踪
- 实现雇主查看申请者信息的功能
- 添加更多的用户资料信息
- 实现更复杂的搜索和筛选功能
- 添加通知系统
此外, 在生产环境中, 你应该:
- 使用更安全的文件上传和存储方法
- 实现更严格的输入验证和安全检查
- 使用事务来确保数据一致性
- 考虑使用更复杂的权限管理系统
- 实现适当的错误处理和日志记录
这个实现为你提供了一个良好的起点, 你可以根据需求继续扩展和完善这个招聘网站。