zyc 44464dd334 feat: DevPerf Dashboard 研发人效看板 v1.0
- 后端:Bun + Hono + Drizzle ORM + SQLite
- 前端:Vue 3 + Naive UI + ECharts
- 项目管理:创建项目 + 绑定 Git 仓库
- OKR 系统:目标/关键结果 CRUD + 进度追踪
- Git 同步:Gitea API 自动同步 commit/PR + 作者关联
- 数据看板:项目 OKR 进度 + KR 状态分布 + 代码活动
- 权限体系:admin/manager/developer/viewer 四级
- Docker 部署:docker-compose + nginx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:57:14 +08:00

90 lines
2.2 KiB
TypeScript

import { eq } from 'drizzle-orm';
import { SignJWT } from 'jose';
import bcrypt from 'bcrypt';
import { db } from '../db/index';
import { users } from '../db/schema';
import { config } from '../config';
import { AppError } from '../middleware/error-handler';
const secret = new TextEncoder().encode(config.JWT_SECRET);
export async function login(email: string, password: string) {
const user = await db.query.users.findFirst({
where: eq(users.email, email),
});
if (!user) {
throw new AppError(40101, 'Invalid email or password', 401);
}
// Check account lockout
if (user.lockedUntil && user.lockedUntil > new Date()) {
const retryAfter = Math.ceil((user.lockedUntil.getTime() - Date.now()) / 1000);
throw new AppError(42300, `Account locked. Try again in ${Math.ceil(retryAfter / 60)} minutes`, 423);
}
// Verify password
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
const attempts = (user.loginAttempts || 0) + 1;
const updateData: Record<string, any> = {
loginAttempts: attempts,
updatedAt: new Date(),
};
if (attempts >= 5) {
updateData.lockedUntil = new Date(Date.now() + 15 * 60 * 1000);
}
await db.update(users)
.set(updateData)
.where(eq(users.id, user.id));
throw new AppError(40101, 'Invalid email or password', 401);
}
// Reset login attempts on success
await db.update(users)
.set({ loginAttempts: 0, lockedUntil: null, updatedAt: new Date() })
.where(eq(users.id, user.id));
// Generate JWT
const token = await new SignJWT({
sub: user.id,
email: user.email,
role: user.role,
displayName: user.displayName,
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
return {
token,
user: {
id: user.id,
displayName: user.displayName,
email: user.email,
role: user.role,
},
};
}
export async function getUserById(userId: string) {
const user = await db.query.users.findFirst({
where: eq(users.id, userId),
});
if (!user) {
throw new AppError(40401, 'User not found', 404);
}
return {
id: user.id,
displayName: user.displayName,
email: user.email,
role: user.role,
};
}