Initial commit
This commit is contained in:
6
app/api/auth/[...nextauth]/route.ts
Normal file
6
app/api/auth/[...nextauth]/route.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import NextAuth from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
71
app/api/recordings/[id]/check-access/route.ts
Normal file
71
app/api/recordings/[id]/check-access/route.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { RecordingService } from "@/lib/services/recording.service";
|
||||
import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION || "us-east-1",
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return Response.json({ error: "未授权" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id: recordingId } = await params;
|
||||
const recording = await RecordingService.getRecordingById(recordingId);
|
||||
|
||||
if (!recording) {
|
||||
return Response.json({ error: "录音不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 检查用户权限
|
||||
if (recording.userId !== session.user.id) {
|
||||
return Response.json({ error: "无权限访问" }, { status: 403 });
|
||||
}
|
||||
|
||||
// 从 S3 URL 提取 bucket 和 key
|
||||
const url = new URL(recording.audioUrl);
|
||||
const pathParts = url.pathname.split("/");
|
||||
const bucket = url.hostname.split(".")[0];
|
||||
const key = pathParts.slice(1).join("/");
|
||||
|
||||
try {
|
||||
// 检查文件是否存在且可访问
|
||||
const command = new HeadObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
await s3.send(command);
|
||||
|
||||
return Response.json({
|
||||
accessible: true,
|
||||
url: recording.audioUrl,
|
||||
size: recording.fileSize,
|
||||
mimeType: recording.mimeType,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("S3 文件访问检查失败:", error);
|
||||
return Response.json({
|
||||
accessible: false,
|
||||
error: "文件无法访问",
|
||||
url: recording.audioUrl,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("检查文件访问失败:", error);
|
||||
return Response.json({ error: "检查文件访问失败" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
98
app/api/recordings/[id]/route.ts
Normal file
98
app/api/recordings/[id]/route.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { RecordingService } from "@/lib/services/recording.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import {
|
||||
AuthenticationError,
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
} from "@/lib/errors/app-error";
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
|
||||
if (!id) {
|
||||
throw new NotFoundError("录音ID不能为空");
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { title } = body;
|
||||
|
||||
console.log(`PUT /api/recordings/${id} - 请求数据:`, {
|
||||
title,
|
||||
userId: session.user.id,
|
||||
});
|
||||
|
||||
if (!title || typeof title !== "string" || title.trim().length === 0) {
|
||||
throw new ValidationError("录音标题不能为空");
|
||||
}
|
||||
|
||||
if (title.length > 100) {
|
||||
throw new ValidationError("录音标题不能超过100个字符");
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Attempting to update recording: ${id} for user: ${
|
||||
session.user.id
|
||||
} with title: "${title.trim()}"`
|
||||
);
|
||||
|
||||
const updatedRecording = await RecordingService.updateRecording(
|
||||
id,
|
||||
session.user.id,
|
||||
{ title: title.trim() }
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Successfully updated recording: ${id}, new title: "${updatedRecording.title}"`
|
||||
);
|
||||
|
||||
return ApiResponseHandler.success(updatedRecording);
|
||||
} catch (error) {
|
||||
console.error(`Failed to update recording ${params}:`, error);
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
|
||||
if (!id) {
|
||||
throw new NotFoundError("录音ID不能为空");
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Attempting to delete recording: ${id} for user: ${session.user.id}`
|
||||
);
|
||||
|
||||
await RecordingService.deleteRecording(id, session.user.id);
|
||||
|
||||
console.log(`Successfully deleted recording: ${id}`);
|
||||
|
||||
return ApiResponseHandler.noContent();
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete recording ${params}:`, error);
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
75
app/api/recordings/[id]/stream/route.ts
Normal file
75
app/api/recordings/[id]/stream/route.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { RecordingService } from "@/lib/services/recording.service";
|
||||
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION || "us-east-1",
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return Response.json({ error: "未授权" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id: recordingId } = await params;
|
||||
const recording = await RecordingService.getRecordingById(recordingId);
|
||||
|
||||
if (!recording) {
|
||||
return Response.json({ error: "录音不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 检查用户权限
|
||||
if (recording.userId !== session.user.id) {
|
||||
return Response.json({ error: "无权限访问" }, { status: 403 });
|
||||
}
|
||||
|
||||
// 从 S3 URL 提取 bucket 和 key
|
||||
const url = new URL(recording.audioUrl);
|
||||
const pathParts = url.pathname.split("/");
|
||||
const bucket = url.hostname.split(".")[0];
|
||||
const key = pathParts.slice(1).join("/");
|
||||
|
||||
try {
|
||||
// 从 S3 获取文件
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
const response = await s3.send(command);
|
||||
const stream = response.Body as ReadableStream;
|
||||
|
||||
if (!stream) {
|
||||
return Response.json({ error: "文件不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
// 返回音频流
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": recording.mimeType,
|
||||
"Content-Length": recording.fileSize.toString(),
|
||||
"Accept-Ranges": "bytes",
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("S3 文件获取失败:", error);
|
||||
return Response.json({ error: "文件无法访问" }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("音频流获取失败:", error);
|
||||
return Response.json({ error: "音频流获取失败" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
40
app/api/recordings/route.ts
Normal file
40
app/api/recordings/route.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { RecordingService } from "@/lib/services/recording.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import { AuthenticationError } from "@/lib/errors/app-error";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get("page") || "1");
|
||||
const limit = parseInt(searchParams.get("limit") || "20");
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const recordings = await RecordingService.getRecordingsByUserId(
|
||||
session.user.id,
|
||||
{
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}
|
||||
);
|
||||
|
||||
// 转换日期格式以匹配前端期望的类型
|
||||
const formattedRecordings = recordings.map((recording) => ({
|
||||
...recording,
|
||||
createdAt: recording.createdAt.toISOString(),
|
||||
}));
|
||||
|
||||
return ApiResponseHandler.success(formattedRecordings);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
141
app/api/recordings/upload/route.ts
Normal file
141
app/api/recordings/upload/route.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { RecordingService } from "@/lib/services/recording.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import { AuthenticationError, ValidationError } from "@/lib/errors/app-error";
|
||||
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION || "us-east-1",
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const audioFile = formData.get("audio") as File;
|
||||
const audioUrl = formData.get("audioUrl") as string;
|
||||
const duration = formData.get("duration") as string;
|
||||
const title = formData.get("title") as string;
|
||||
const fileSize = formData.get("fileSize") as string;
|
||||
const mimeType = formData.get("mimeType") as string;
|
||||
|
||||
let finalAudioUrl: string;
|
||||
let finalFileSize: number;
|
||||
let finalMimeType: string;
|
||||
|
||||
// 如果有音频文件,先上传到 S3
|
||||
if (audioFile) {
|
||||
if (!duration) {
|
||||
throw new ValidationError("缺少录音时长");
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
if (!audioFile.type.startsWith("audio/")) {
|
||||
throw new ValidationError("文件类型必须是音频");
|
||||
}
|
||||
|
||||
// 验证文件大小 (50MB 限制)
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
if (audioFile.size > maxSize) {
|
||||
throw new ValidationError("文件大小不能超过 50MB");
|
||||
}
|
||||
|
||||
// 生成唯一的文件名
|
||||
const userId = session.user.id;
|
||||
const timestamp = Date.now();
|
||||
const uniqueFileName = `recordings/${userId}/${timestamp}-recording.webm`;
|
||||
|
||||
// 上传到 S3
|
||||
const arrayBuffer = await audioFile.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: process.env.AWS_S3_BUCKET!,
|
||||
Key: uniqueFileName,
|
||||
Body: buffer,
|
||||
ContentType: audioFile.type,
|
||||
});
|
||||
|
||||
await s3.send(command);
|
||||
|
||||
// 生成 S3 URL - 修复格式
|
||||
const region = process.env.AWS_REGION || "us-east-1";
|
||||
const bucketName = process.env.AWS_S3_BUCKET!;
|
||||
|
||||
// 根据区域生成正确的 S3 URL
|
||||
let s3Url: string;
|
||||
if (region === "us-east-1") {
|
||||
// us-east-1 使用特殊格式
|
||||
s3Url = `https://${bucketName}.s3.amazonaws.com/${uniqueFileName}`;
|
||||
} else {
|
||||
// 其他区域使用标准格式
|
||||
s3Url = `https://${bucketName}.s3.${region}.amazonaws.com/${uniqueFileName}`;
|
||||
}
|
||||
|
||||
finalAudioUrl = s3Url;
|
||||
finalFileSize = audioFile.size;
|
||||
finalMimeType = audioFile.type;
|
||||
} else if (audioUrl) {
|
||||
// 如果提供了 S3 URL,直接使用
|
||||
if (!fileSize || !mimeType) {
|
||||
throw new ValidationError("缺少文件信息");
|
||||
}
|
||||
|
||||
// 验证 S3 URL 格式
|
||||
if (
|
||||
!audioUrl.startsWith("https://") ||
|
||||
!audioUrl.includes("amazonaws.com")
|
||||
) {
|
||||
throw new ValidationError("无效的音频文件URL");
|
||||
}
|
||||
|
||||
finalAudioUrl = audioUrl;
|
||||
finalFileSize = parseInt(fileSize);
|
||||
finalMimeType = mimeType;
|
||||
} else {
|
||||
throw new ValidationError("缺少音频文件或URL");
|
||||
}
|
||||
|
||||
if (!duration) {
|
||||
throw new ValidationError("缺少录音时长");
|
||||
}
|
||||
|
||||
// 验证文件大小 (50MB 限制)
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
if (finalFileSize > maxSize) {
|
||||
throw new ValidationError("文件大小不能超过 50MB");
|
||||
}
|
||||
|
||||
// 验证标题
|
||||
const recordingTitle =
|
||||
title?.trim() || `录音 ${new Date().toLocaleString("zh-CN")}`;
|
||||
if (recordingTitle.length > 100) {
|
||||
throw new ValidationError("录音标题不能超过100个字符");
|
||||
}
|
||||
|
||||
// 创建录音记录
|
||||
const recording = await RecordingService.createRecording({
|
||||
title: recordingTitle,
|
||||
audioUrl: finalAudioUrl,
|
||||
duration: parseInt(duration),
|
||||
fileSize: finalFileSize,
|
||||
mimeType: finalMimeType,
|
||||
userId: session.user.id,
|
||||
});
|
||||
|
||||
return ApiResponseHandler.created(recording);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
42
app/api/register/route.ts
Normal file
42
app/api/register/route.ts
Normal file
@ -0,0 +1,42 @@
|
||||
// app/api/register/route.ts
|
||||
|
||||
import { NextRequest } from "next/server";
|
||||
import { UserService } from "@/lib/services/user.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import { ValidationError } from "@/lib/errors/app-error";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, name, password } = body;
|
||||
|
||||
// 验证数据完整性
|
||||
if (!email || !name || !password) {
|
||||
throw new ValidationError("缺少邮箱、姓名或密码");
|
||||
}
|
||||
|
||||
// 验证密码长度
|
||||
if (password.length < 6) {
|
||||
throw new ValidationError("密码长度至少6位");
|
||||
}
|
||||
|
||||
// 使用服务层创建用户
|
||||
const user = await UserService.createUser({
|
||||
email,
|
||||
name,
|
||||
password,
|
||||
});
|
||||
|
||||
// 返回成功响应(不包含敏感信息)
|
||||
const userProfile = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
createdAt: user.createdAt,
|
||||
};
|
||||
|
||||
return ApiResponseHandler.created(userProfile);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
50
app/api/upload/presign/route.ts
Normal file
50
app/api/upload/presign/route.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
|
||||
const s3 = new S3Client({
|
||||
region: process.env.AWS_REGION || "us-east-1",
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
// 验证用户身份
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.email) {
|
||||
return Response.json({ error: "未授权" }, { status: 401 });
|
||||
}
|
||||
|
||||
const { fileName, fileType } = await req.json();
|
||||
|
||||
if (!fileName || !fileType) {
|
||||
return Response.json({ error: "缺少必要参数" }, { status: 400 });
|
||||
}
|
||||
|
||||
// 生成唯一的文件名,包含用户ID和时间戳
|
||||
const userId = session.user.id || session.user.email;
|
||||
const timestamp = Date.now();
|
||||
const uniqueFileName = `recordings/${userId}/${timestamp}-${fileName}`;
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: process.env.AWS_S3_BUCKET!,
|
||||
Key: uniqueFileName,
|
||||
ContentType: fileType,
|
||||
});
|
||||
|
||||
const url = await getSignedUrl(s3, command, { expiresIn: 300 }); // 5分钟有效
|
||||
|
||||
return Response.json({
|
||||
url,
|
||||
fileName: uniqueFileName,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("生成上传凭证失败:", error);
|
||||
return Response.json({ error: "生成上传凭证失败" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
60
app/api/user/export/route.ts
Normal file
60
app/api/user/export/route.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { RecordingService } from "@/lib/services/recording.service";
|
||||
import { UserSettingsService } from "@/lib/services/user-settings.service";
|
||||
import { UserService } from "@/lib/services/user.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import { AuthenticationError } from "@/lib/errors/app-error";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
// 获取用户数据
|
||||
const [userProfile, userSettings, recordings] = await Promise.all([
|
||||
UserService.getUserById(session.user.id),
|
||||
UserSettingsService.getUserSettings(session.user.id),
|
||||
RecordingService.getRecordingsByUserId(session.user.id),
|
||||
]);
|
||||
|
||||
// 构建导出数据
|
||||
const exportData = {
|
||||
user: {
|
||||
id: userProfile?.id,
|
||||
name: userProfile?.name,
|
||||
email: userProfile?.email,
|
||||
createdAt: userProfile?.createdAt,
|
||||
updatedAt: userProfile?.updatedAt,
|
||||
},
|
||||
settings: userSettings,
|
||||
recordings: recordings.map((recording) => ({
|
||||
id: recording.id,
|
||||
title: recording.title,
|
||||
audioUrl: recording.audioUrl,
|
||||
duration: recording.duration,
|
||||
fileSize: recording.fileSize,
|
||||
mimeType: recording.mimeType,
|
||||
createdAt: recording.createdAt.toISOString(),
|
||||
})),
|
||||
exportDate: new Date().toISOString(),
|
||||
version: "1.0.0",
|
||||
};
|
||||
|
||||
// 返回 JSON 文件
|
||||
const response = new Response(JSON.stringify(exportData, null, 2), {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Disposition": `attachment; filename="recorder-export-${Date.now()}.json"`,
|
||||
},
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
51
app/api/user/profile/route.ts
Normal file
51
app/api/user/profile/route.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { UserService } from "@/lib/services/user.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import { AuthenticationError, ValidationError } from "@/lib/errors/app-error";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const userProfile = await UserService.getUserById(session.user.id);
|
||||
|
||||
if (!userProfile) {
|
||||
throw new AuthenticationError("用户不存在");
|
||||
}
|
||||
|
||||
return ApiResponseHandler.success(userProfile);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { name } = body;
|
||||
|
||||
if (!name || typeof name !== "string") {
|
||||
throw new ValidationError("名称不能为空");
|
||||
}
|
||||
|
||||
const updatedProfile = await UserService.updateUser(session.user.id, {
|
||||
name,
|
||||
});
|
||||
|
||||
return ApiResponseHandler.success(updatedProfile);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
81
app/api/user/settings/route.ts
Normal file
81
app/api/user/settings/route.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { UserSettingsService } from "@/lib/services/user-settings.service";
|
||||
import { ApiResponseHandler } from "@/lib/utils/api-response";
|
||||
import { AuthenticationError, ValidationError } from "@/lib/errors/app-error";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
// 获取或创建用户设置
|
||||
const settings = await UserSettingsService.getOrCreateUserSettings(
|
||||
session.user.id
|
||||
);
|
||||
|
||||
return ApiResponseHandler.success(settings);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
const { defaultQuality, publicProfile, allowDownload } =
|
||||
await request.json();
|
||||
|
||||
// 验证音频质量
|
||||
if (
|
||||
defaultQuality &&
|
||||
!["low", "medium", "high", "lossless"].includes(defaultQuality)
|
||||
) {
|
||||
throw new ValidationError("无效的音频质量设置");
|
||||
}
|
||||
|
||||
// 更新用户设置
|
||||
const settings = await UserSettingsService.updateUserSettings(
|
||||
session.user.id,
|
||||
{
|
||||
defaultQuality,
|
||||
publicProfile:
|
||||
typeof publicProfile === "boolean" ? publicProfile : undefined,
|
||||
allowDownload:
|
||||
typeof allowDownload === "boolean" ? allowDownload : undefined,
|
||||
}
|
||||
);
|
||||
|
||||
return ApiResponseHandler.success(settings);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE() {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
// 重置用户设置为默认值
|
||||
const settings = await UserSettingsService.resetUserSettings(
|
||||
session.user.id
|
||||
);
|
||||
|
||||
return ApiResponseHandler.success(settings);
|
||||
} catch (error) {
|
||||
return ApiResponseHandler.error(error as Error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user