好的, 我们来进一步扩展这个招聘网站, 添加您提到的功能。我们将添加个人信息编辑、文件上传、高级职位搜索和过滤, 以及雇主发布岗位的功能。这个版本会更加复杂, 所以我们将分模块来实现:
- 首先, 更新数据库结构:
-- 更新users表
ALTER TABLE users ADD COLUMN resume_url TEXT;
ALTER TABLE users ADD COLUMN education_cert_url TEXT;
ALTER TABLE users ADD COLUMN bio TEXT;
ALTER TABLE users ADD COLUMN skills TEXT;
-- 创建employers表
CREATE TABLE employers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
company_name TEXT NOT NULL,
company_description TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 更新jobs表
ALTER TABLE jobs ADD COLUMN employer_id INTEGER;
ALTER TABLE jobs ADD COLUMN location TEXT;
ALTER TABLE jobs ADD COLUMN salary TEXT;
ALTER TABLE jobs ADD COLUMN job_type TEXT;- 安装文件上传处理库:
bun add @elysiajs/multipart- 更新 src/index. Ts:
import { Elysia, t } from 'elysia'
import { html } from '@elysiajs/html'
import { staticPlugin } from '@elysiajs/static'
import { cookie } from '@elysiajs/cookie'
import { jwt } from '@elysiajs/jwt'
import { multipart } from '@elysiajs/multipart'
import { Client } from '@libsql/client'
import * as oauth from 'oauth4webapi'
import { writeFile, mkdir } from 'fs/promises'
import { join } from 'path'
const db = new Client({
url: 'YOUR_TURSO_DATABASE_URL',
authToken: 'YOUR_TURSO_AUTH_TOKEN'
})
const app = new Elysia()
.use(html())
.use(staticPlugin())
.use(cookie())
.use(jwt({
name: 'jwt',
secret: 'your-jwt-secret'
}))
.use(multipart())
// ... (保留之前的OAuth配置和isAuthenticated函数)
// 主页
.get('/', async (context) => {
const authenticated = await isAuthenticated(context)
return `
<!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="/htmx.min.js"></script>
</head>
<body>
<h1>Job Board</h1>
${authenticated ? `
<button hx-post="/logout" hx-swap="outerHTML">Logout</button>
<a href="/profile">My Profile</a>
` : `
<a href="/auth/google">Login with Google</a>
<a href="/auth/github">Login with GitHub</a>
`}
<div>
<input type="text" name="search" placeholder="Search jobs" hx-get="/jobs" hx-trigger="keyup changed delay:500ms" hx-target="#job-list">
<button hx-get="/job-filters" hx-target="#filters">Advanced Filters</button>
</div>
<div id="filters"></div>
<div id="job-list" hx-get="/jobs" hx-trigger="load"></div>
${authenticated ? `
<button hx-get="/employer/job-form" hx-target="#job-form">Post a Job</button>
<div id="job-form"></div>
` : ''}
</body>
</html>
`
})
// 个人资料页面
.get('/profile', async ({ jwt, cookie: { auth } }) => {
if (!auth) return 'Please login to view your profile'
const { sub: userId } = await jwt.verify(auth)
const user = await db.execute({
sql: 'SELECT * FROM users WHERE id = ?',
args: [userId]
})
return `
<h2>My Profile</h2>
<form hx-post="/profile" hx-encoding="multipart/form-data">
<input type="text" name="name" value="${user.rows[0].name}" placeholder="Name">
<textarea name="bio" placeholder="Bio">${user. Rows[0]. Bio || ''}</textarea>
<input type="text" name="skills" value="${user.rows[0].skills || ''}" placeholder="Skills (comma separated)">
<input type="file" name="resume" accept=".pdf,.doc,.docx">
<input type="file" name="education_cert" accept=".pdf,.jpg,.jpeg,.png">
<button type="submit">Update Profile</button>
</form>
`
})
// 更新个人资料
.post ('/profile', async ({ body, jwt, cookie: { auth } }) => {
if (!auth) return 'Please login to update your profile'
const { sub: userId } = await jwt.verify(auth)
const { name, bio, skills, resume, education_cert } = body
let resumeUrl = null
let educationCertUrl = null
if (resume) {
const fileName = `${userId}_resume_${Date.now()}.${resume.name.split('.').pop()}`
await mkdir(join(process.cwd(), 'uploads'), { recursive: true })
await writeFile(join(process.cwd(), 'uploads', fileName), resume.buffer)
resumeUrl = `/uploads/${fileName}`
}
if (education_cert) {
const fileName = `${userId}_edu_${Date.now()}.${education_cert.name.split('.').pop()}`
await mkdir(join(process.cwd(), 'uploads'), { recursive: true })
await writeFile(join(process.cwd(), 'uploads', fileName), education_cert.buffer)
educationCertUrl = `/uploads/${fileName}`
}
await db.execute({
sql: `UPDATE users SET name = ?, bio = ?, skills = ?,
resume_url = COALESCE(?, resume_url),
education_cert_url = COALESCE(?, education_cert_url)
WHERE id = ?`,
args: [name, bio, skills, resumeUrl, educationCertUrl, userId]
})
return 'Profile updated successfully!'
})
// 职位列表和搜索
.get('/jobs', async ({ query }) => {
const { search, location, job_type, salary_min, salary_max } = query
let sql = 'SELECT * FROM jobs WHERE 1=1'
const args = []
if (search) {
sql += ' AND title LIKE ?'
args.push(`%${search}%`)
}
if (location) {
sql += ' AND location = ?'
args.push(location)
}
if (job_type) {
sql += ' AND job_type = ?'
args.push(job_type)
}
if (salary_min) {
sql += ' AND CAST(SUBSTRING(salary, 1, INSTR(salary, "-")-1) AS INTEGER) >= ?'
args.push(parseInt(salary_min))
}
if (salary_max) {
sql += ' AND CAST(SUBSTRING(salary, INSTR(salary, "-")+1) AS INTEGER) <= ?'
args.push(parseInt(salary_max))
}
sql += ' ORDER BY created_at DESC'
const jobs = await db.execute({ sql, args })
return jobs.rows.map(job => `
<div>
<h2>${job.title}</h2>
<p>${job.company}</p>
<p>${job.location} | ${job.job_type} | ${job.salary}</p>
<p>${job.description}</p>
<button hx-post="/apply/${job.id}" hx-swap="outerHTML">Apply</button>
</div>
`).join('')
})
// 高级过滤器
.get('/job-filters', () => `
<form hx-get="/jobs" hx-target="#job-list">
<input type="text" name="location" placeholder="Location">
<select name="job_type">
<option value="">All Types</option>
<option value="Full-time">Full-time</option>
<option value="Part-time">Part-time</option>
<option value="Contract">Contract</option>
</select>
<input type="number" name="salary_min" placeholder="Min Salary">
<input type="number" name="salary_max" placeholder="Max Salary">
<button type="submit">Apply Filters</button>
</form>
`)
// 雇主发布工作表单
.get ('/employer/job-form', () => `
<form hx-post="/employer/jobs" hx-target="#job-list">
<input name="title" placeholder="Job Title" required>
<input name="company" placeholder="Company" required>
<textarea name="description" placeholder="Job Description" required></textarea>
<input name="location" placeholder="Location" required>
<input name="salary" placeholder="Salary Range (e.g. 50000-70000)" required>
<select name="job_type" required>
<option value="Full-time">Full-time</option>
<option value="Part-time">Part-time</option>
<option value="Contract">Contract</option>
</select>
<button type="submit">Post Job</button>
</form>
`)
// 雇主发布工作
.post ('/employer/jobs', async ({ body, jwt, cookie: { auth } }) => {
if (!auth) return 'Please login to post a job'
const { sub: userId } = await jwt.verify(auth)
const { title, company, description, location, salary, job_type } = body
// 检查用户是否是雇主
let employer = await db.execute({
sql: 'SELECT * FROM employers WHERE user_id = ?',
args: [userId]
})
if (employer.rows.length === 0) {
// 如果不是雇主,创建雇主记录
await db.execute({
sql: 'INSERT INTO employers (user_id, company_name) VALUES (?, ?)',
args: [userId, company]
})
employer = await db.execute({
sql: 'SELECT * FROM employers WHERE user_id = ?',
args: [userId]
})
}
await db.execute({
sql: `INSERT INTO jobs (title, company, description, employer_id, location, salary, job_type)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
args: [title, company, description, employer.rows[0].id, location, salary, job_type]
})
const jobs = await db.execute('SELECT * FROM jobs ORDER BY created_at DESC')
return jobs.rows.map(job => `
<div>
<h2>${job.title}</h2>
<p>${job.company}</p>
<p>${job.location} | ${job.job_type} | ${job.salary}</p>
<p>${job.description}</p>
<button hx-post="/apply/${job.id}" hx-swap="outerHTML">Apply</button>
</div>
`).join('')
})
// ... (保留之前的OAuth路由和其他功能)
.listen(3000)
console.log(`Elysia is running at http://localhost:3000`)这个扩展版本添加了以下新功能:
- 个人资料编辑: 用户可以更新他们的个人信息, 包括姓名、简历和学历证明。
- 文件上传: 用户可以上传简历和学历证明文件。
- 高级职位搜索和过滤: 用户可以根据位置、工作类型和薪资范围过滤职位。
- 雇主功能: 雇主可以发布新的工作岗位。
要使这个版本正常工作, 你需要:
- 更新 Turso 数据库结构。
- 创建一个
uploads文件夹来存储上传的文件。 - 确保你的服务器有足够的存储空间来处理文件上传。
- 考虑实现文件类型和大小的验证, 以及可能的云存储集成, 以更好地处理文件上传。
这个版本提供了一个更全面的招聘网站框架, 包括求职者和雇主的核心功能。你可以根据需要进一步优化 UI/UX, 添加更多的错误处理, 实现更复杂的搜索算法, 或者添加其他功能, 如应用跟踪系统、面试安排等。