刪除不必要檔案

This commit is contained in:
2025-09-09 15:19:04 +08:00
parent 46bd9db2e3
commit 3369e3fc0f
86 changed files with 198 additions and 11011 deletions

View File

@@ -1,216 +0,0 @@
import jwt from 'jsonwebtoken';
import { NextRequest } from 'next/server';
import { db } from './database';
import bcrypt from 'bcrypt';
// JWT 配置
const JWT_SECRET = process.env.JWT_SECRET || 'good777';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
// 用戶角色類型
export type UserRole = 'user' | 'developer' | 'admin';
// 用戶介面
export interface User {
id: string;
name: string;
email: string;
avatar?: string;
department: string;
role: UserRole;
joinDate: string;
totalLikes: number;
totalViews: number;
createdAt: string;
updatedAt: string;
}
// JWT Payload 介面
export interface JWTPayload {
userId: string;
email: string;
role: UserRole;
iat: number;
exp: number;
}
// 生成 JWT Token
export function generateToken(user: { id: string; email: string; role: UserRole }): string {
const payload: Omit<JWTPayload, 'iat' | 'exp'> = {
userId: user.id,
email: user.email,
role: user.role
};
return jwt.sign(payload, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN
});
}
// 驗證 JWT Token
export function verifyToken(token: string): JWTPayload | null {
try {
return jwt.verify(token, JWT_SECRET) as JWTPayload;
} catch (error) {
console.error('Token verification failed:', error);
return null;
}
}
// 從請求中提取 Token
export function extractToken(request: NextRequest): string | null {
const authHeader = request.headers.get('authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return null;
}
return authHeader.substring(7);
}
// 驗證用戶權限
export function hasPermission(userRole: UserRole, requiredRole: UserRole): boolean {
const roleHierarchy = {
user: 1,
developer: 2,
admin: 3
};
return roleHierarchy[userRole] >= roleHierarchy[requiredRole];
}
// 用戶認證中間件
export async function authenticateUser(request: NextRequest): Promise<User | null> {
try {
const token = extractToken(request);
if (!token) {
console.log('No token found in request');
return null;
}
const payload = verifyToken(token);
if (!payload) {
console.log('Token verification failed');
return null;
}
console.log('Token payload:', payload);
// 從資料庫獲取最新用戶資料
const users = await db.query<User>(
'SELECT * FROM users WHERE id = ? AND email = ?',
[payload.userId, payload.email]
);
const user = users.length > 0 ? users[0] : null;
console.log('Database query result:', user);
return user;
} catch (error) {
console.error('Authentication error:', error);
return null;
}
}
// 檢查管理員權限
export async function requireAdmin(request: NextRequest): Promise<User> {
const user = await authenticateUser(request);
if (!user) {
throw new Error('Authentication required');
}
if (user.role !== 'admin') {
throw new Error('Admin permission required');
}
return user;
}
// 檢查開發者或管理員權限
export async function requireDeveloperOrAdmin(request: NextRequest): Promise<User> {
const user = await authenticateUser(request);
if (!user) {
throw new Error('Authentication required');
}
if (user.role !== 'developer' && user.role !== 'admin') {
throw new Error('Developer or admin permission required');
}
return user;
}
// 密碼驗證
export async function validatePassword(password: string): Promise<{ isValid: boolean; errors: string[] }> {
const errors: string[] = [];
if (password.length < 8) {
errors.push('密碼長度至少需要8個字符');
}
if (!/[A-Z]/.test(password)) {
errors.push('密碼需要包含至少一個大寫字母');
}
if (!/[a-z]/.test(password)) {
errors.push('密碼需要包含至少一個小寫字母');
}
if (!/\d/.test(password)) {
errors.push('密碼需要包含至少一個數字');
}
return {
isValid: errors.length === 0,
errors
};
}
// 加密密碼
export async function hashPassword(password: string): Promise<string> {
const saltRounds = 12;
return bcrypt.hash(password, saltRounds);
}
// 驗證密碼
export async function comparePassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// 生成隨機密碼
export function generateRandomPassword(length: number = 12): string {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
let password = '';
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
return password;
}
// 用戶資料驗證
export function validateUserData(data: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
if (!data.name || data.name.trim().length < 2) {
errors.push('姓名至少需要2個字符');
}
if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.push('請提供有效的電子郵件地址');
}
if (!data.department || data.department.trim().length < 2) {
errors.push('部門名稱至少需要2個字符');
}
if (data.role && !['user', 'developer', 'admin'].includes(data.role)) {
errors.push('無效的用戶角色');
}
return {
isValid: errors.length === 0,
errors
};
}

View File

@@ -1,174 +0,0 @@
import fs from 'fs';
import path from 'path';
// 日誌級別
export enum LogLevel {
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3
}
// 日誌配置
const LOG_LEVEL = (process.env.LOG_LEVEL as LogLevel) || LogLevel.INFO;
const LOG_FILE = process.env.LOG_FILE || './logs/app.log';
// 確保日誌目錄存在
const logDir = path.dirname(LOG_FILE);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// 日誌顏色
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
yellow: '\x1b[33m',
green: '\x1b[32m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
// 日誌級別名稱
const levelNames = {
[LogLevel.ERROR]: 'ERROR',
[LogLevel.WARN]: 'WARN',
[LogLevel.INFO]: 'INFO',
[LogLevel.DEBUG]: 'DEBUG'
};
// 日誌級別顏色
const levelColors = {
[LogLevel.ERROR]: colors.red,
[LogLevel.WARN]: colors.yellow,
[LogLevel.INFO]: colors.green,
[LogLevel.DEBUG]: colors.blue
};
// 寫入檔案日誌
function writeToFile(level: LogLevel, message: string, data?: any) {
const timestamp = new Date().toISOString();
const levelName = levelNames[level];
const logEntry = {
timestamp,
level: levelName,
message,
data: data || null
};
const logLine = JSON.stringify(logEntry) + '\n';
try {
fs.appendFileSync(LOG_FILE, logLine);
} catch (error) {
console.error('Failed to write to log file:', error);
}
}
// 控制台輸出
function consoleOutput(level: LogLevel, message: string, data?: any) {
const timestamp = new Date().toISOString();
const levelName = levelNames[level];
const color = levelColors[level];
let output = `${color}[${levelName}]${colors.reset} ${timestamp} - ${message}`;
if (data) {
output += `\n${color}Data:${colors.reset} ${JSON.stringify(data, null, 2)}`;
}
console.log(output);
}
// 主日誌函數
function log(level: LogLevel, message: string, data?: any) {
if (level <= LOG_LEVEL) {
consoleOutput(level, message, data);
writeToFile(level, message, data);
}
}
// 日誌類別
export class Logger {
private context: string;
constructor(context: string = 'App') {
this.context = context;
}
error(message: string, data?: any) {
log(LogLevel.ERROR, `[${this.context}] ${message}`, data);
}
warn(message: string, data?: any) {
log(LogLevel.WARN, `[${this.context}] ${message}`, data);
}
info(message: string, data?: any) {
log(LogLevel.INFO, `[${this.context}] ${message}`, data);
}
debug(message: string, data?: any) {
log(LogLevel.DEBUG, `[${this.context}] ${message}`, data);
}
// API 請求日誌
logRequest(method: string, url: string, statusCode: number, duration: number, userId?: string) {
this.info('API Request', {
method,
url,
statusCode,
duration: `${duration}ms`,
userId
});
}
// 認證日誌
logAuth(action: string, email: string, success: boolean, ip?: string) {
this.info('Authentication', {
action,
email,
success,
ip
});
}
// 資料庫操作日誌
logDatabase(operation: string, table: string, duration: number, success: boolean) {
this.debug('Database Operation', {
operation,
table,
duration: `${duration}ms`,
success
});
}
// 錯誤日誌
logError(error: Error, context?: string) {
this.error('Application Error', {
message: error.message,
stack: error.stack,
context: context || this.context
});
}
// 活動日誌
logActivity(userId: string, entityType: string, entityId: string, action: string, data?: any) {
this.info('User Activity', {
userId,
entityType,
entityId,
action,
data
});
}
}
// 預設日誌實例
export const logger = new Logger();
// 建立特定上下文的日誌實例
export function createLogger(context: string): Logger {
return new Logger(context);
}

View File

@@ -596,8 +596,8 @@ export class UserService {
SELECT
COUNT(*) as total_apps,
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_apps_this_month,
COALESCE(SUM(view_count), 0) as total_views,
COALESCE(SUM(like_count), 0) as total_likes
COALESCE(SUM(views_count), 0) as total_views,
COALESCE(SUM(likes_count), 0) as total_likes
FROM apps
WHERE is_active = TRUE
`;
@@ -676,10 +676,10 @@ export class UserService {
FROM users
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT ?
LIMIT ${limit}
`;
const activities = await this.query(sql, [limit]);
const activities = await this.query(sql);
return activities.map(activity => ({
id: `user_${activity.activity_time}`,
@@ -703,20 +703,20 @@ export class UserService {
a.id,
a.name,
a.description,
a.view_count as views,
a.like_count as likes,
a.views_count as views,
a.likes_count as likes,
COALESCE(AVG(ur.rating), 0) as rating,
a.category,
a.created_at
FROM apps a
LEFT JOIN user_ratings ur ON a.id = ur.app_id
WHERE a.is_active = TRUE
GROUP BY a.id, a.name, a.description, a.view_count, a.like_count, a.category, a.created_at
ORDER BY (a.view_count + a.like_count * 2) DESC
LIMIT ?
GROUP BY a.id, a.name, a.description, a.views_count, a.likes_count, a.category, a.created_at
ORDER BY (a.views_count + a.likes_count * 2) DESC
LIMIT ${limit}
`;
const apps = await this.query(sql, [limit]);
const apps = await this.query(sql);
return apps.map(app => ({
id: app.id,