好的, 我们来扩展这个招聘网站, 添加职位搜索、应用职位功能, 并实现用户认证, 包括 Google 和 GitHub 的 OAuth 2 登录。这个扩展版本会相对复杂一些, 我们将分步骤实现:
- 更新依赖:
bun add @elysiajs/cookie @elysiajs/jwt
bun add oauth4webapi- 更新数据库结构:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT,
auth_provider TEXT NOT NULL
);
CREATE TABLE applications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (job_id) REFERENCES jobs(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Add an index to improve search performance
CREATE INDEX idx_jobs_title ON jobs(title);- 更新 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 { Client } from '@libsql/client'
import * as oauth from 'oauth4webapi'
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'
}))
// OAuth2 configurations
const googleConfig = {
clientId: 'YOUR_GOOGLE_CLIENT_ID',
clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET',
redirectUri: 'http://localhost:3000/auth/google/callback'
}
const githubConfig = {
clientId: 'YOUR_GITHUB_CLIENT_ID',
clientSecret: 'YOUR_GITHUB_CLIENT_SECRET',
redirectUri: 'http://localhost:3000/auth/github/callback'
}
// Helper function to check if user is authenticated
const isAuthenticated = async ({ jwt, cookie: { auth } }) => {
if (!auth) return false
try {
await jwt.verify(auth)
return true
} catch {
return false
}
}
// Main page
.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="/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">
</div>
<div id="job-list" hx-get="/jobs" hx-trigger="load"></div>
${authenticated ? `
<button hx-get="/job-form" hx-target="#job-form">Post a Job</button>
<div id="job-form"></div>
` : ''}
</body>
</html>
`
})
// Job list with search
.get('/jobs', async ({ query }) => {
const search = query.search || ''
const jobs = await db.execute({
Sql: 'SELECT * FROM jobs WHERE title LIKE ? ORDER BY created_at DESC',
Args: [`%${search}%`]
})
return jobs.rows.map(job => `
<div>
<h2>${job.title}</h2>
<p>${job.company}</p>
<p>${job.description}</p>
<button hx-post="/apply/${job.id}" hx-swap="outerHTML">Apply</button>
</div>
`).join('')
})
// Job form
.get('/job-form', () => `
<form hx-post="/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>
<button type="submit">Post Job</button>
</form>
`)
// Post new job
.post('/jobs', async ({ body }) => {
const { title, company, description } = body
await db.execute({
sql: 'INSERT INTO jobs (title, company, description) VALUES (?, ?, ?)',
args: [title, company, description]
})
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.description}</p>
<button hx-post="/apply/${job.id}" hx-swap="outerHTML">Apply</button>
</div>
`).join('')
})
// Apply for a job
.post('/apply/:id', async ({ params, jwt, cookie: { auth } }) => {
if (!auth) return 'Please login to apply'
const { sub: userId } = await jwt.verify(auth)
const jobId = params.id
await db.execute({
sql: 'INSERT INTO applications (job_id, user_id) VALUES (?, ?)',
args: [jobId, userId]
})
return 'Application submitted successfully!'
})
// Google OAuth2 login
.get('/auth/google', async ({ set }) => {
const authorizationUrl = oauth.generateAuthorizationURL(
'https://accounts.google.com',
googleConfig.clientId,
googleConfig.redirectUri,
{ scope: 'email profile' }
)
set.redirect = authorizationUrl
})
// Google OAuth2 callback
.get('/auth/google/callback', async ({ query, set, jwt, setCookie }) => {
const tokens = await oauth.getTokens(
'https://accounts.google.com',
googleConfig.clientId,
googleConfig.clientSecret,
googleConfig.redirectUri,
query.code
)
const userInfo = await oauth.getUserInfo('https://www.googleapis.com/oauth2/v3/userinfo', tokens.access_token)
let user = await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [userInfo.email]
})
if (user.rows.length === 0) {
await db.execute({
sql: 'INSERT INTO users (email, name, auth_provider) VALUES (?, ?, ?)',
args: [userInfo.email, userInfo.name, 'google']
})
user = await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [userInfo.email]
})
}
const token = await jwt.sign({ sub: user.rows[0].id })
setCookie('auth', token, { httpOnly: true })
Set. Redirect = '/'
})
// GitHub OAuth2 login
.get('/auth/github', async ({ set }) => {
Const authorizationUrl = oauth.GenerateAuthorizationURL (
'https://github.com',
githubConfig.clientId,
githubConfig.redirectUri,
{ scope: 'user:email' }
)
set.redirect = authorizationUrl
})
// GitHub OAuth2 callback
.get('/auth/github/callback', async ({ query, set, jwt, setCookie }) => {
const tokens = await oauth.getTokens(
'https://github.com',
githubConfig.clientId,
githubConfig.clientSecret,
githubConfig.redirectUri,
query.code
)
const userInfo = await oauth.getUserInfo('https://api.github.com/user', tokens.access_token)
let user = await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [userInfo.email]
})
if (user.rows.length === 0) {
await db.execute({
sql: 'INSERT INTO users (email, name, auth_provider) VALUES (?, ?, ?)',
args: [userInfo.email, userInfo.name, 'github']
})
user = await db.execute({
sql: 'SELECT * FROM users WHERE email = ?',
args: [userInfo.email]
})
}
const token = await jwt.sign({ sub: user.rows[0].id })
setCookie('auth', token, { httpOnly: true })
set.redirect = '/'
})
// Logout
.post('/logout', ({ setCookie, set }) => {
setCookie('auth', '', { maxAge: 0 })
set.redirect = '/'
})
.listen(3000)
console.log(`Elysia is running at http://localhost:3000`)这个扩展版本添加了以下功能:
- 用户认证: 支持 Google 和 GitHub 的 OAuth2登录。
- 职位搜索: 用户可以通过标题搜索职位。
- 应用职位: 已登录的用户可以申请职位。
- 用户会话管理: 使用 JWT 来维护用户会话。
要使这个版本工作, 你需要:
- 在 Google Cloud Console 和 GitHub Developer settings 中创建 OAuth 应用, 并获取客户端 ID 和密钥。
- 更新 Turso 数据库结构。
- 替换代码中的占位符 (如数据库 URL、OAuth 客户端 ID 和密钥等)为实际值。
- 确保
public文件夹中有htmx.min.js文件。
这个版本提供了一个更完整的招聘网站框架, 包括用户认证、职位搜索和申请功能。你可以根据需要进一步优化 UI/UX, 添加错误处理, 以及实现更多功能, 如用户档案、应用跟踪等。