update
This commit is contained in:
@@ -14,3 +14,12 @@ mysqld --basedir=/usr \
|
|||||||
--socket=/home/aecw/re-AstarCupWeb/database/mysql.sock \
|
--socket=/home/aecw/re-AstarCupWeb/database/mysql.sock \
|
||||||
--pid-file=/home/aecw/re-AstarCupWeb/database/mysql.pid &
|
--pid-file=/home/aecw/re-AstarCupWeb/database/mysql.pid &
|
||||||
``
|
``
|
||||||
|
|
||||||
|
# env
|
||||||
|
``
|
||||||
|
OSU_CLIENT_ID =
|
||||||
|
OSU_CLIENT_SECRET =
|
||||||
|
OSU_REDIRECT_URI =
|
||||||
|
DATABASE_URL=""
|
||||||
|
DATABASE_CLIENT_URL=""
|
||||||
|
``
|
||||||
88
app/api/auth/callback/osu/route.ts
Normal file
88
app/api/auth/callback/osu/route.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getOsuToken, getMyOsuInfo } from '@/app/lib/osuAuth';
|
||||||
|
import { CreateUser } from '@/app/lib/UserOperation';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
const code = searchParams.get('code');
|
||||||
|
const error = searchParams.get('error');
|
||||||
|
const errorDescription = searchParams.get('error_description');
|
||||||
|
|
||||||
|
// 检查错误
|
||||||
|
if (error) {
|
||||||
|
console.error('OAuth error:', error, errorDescription);
|
||||||
|
return NextResponse.redirect(new URL('/debug?error=' + encodeURIComponent(errorDescription || error), request.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查授权码
|
||||||
|
if (!code) {
|
||||||
|
console.error('No authorization code provided');
|
||||||
|
return NextResponse.redirect(new URL('/debug?error=No authorization code provided', request.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Received OAuth code:', code);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取访问令牌
|
||||||
|
const tokenData = await getOsuToken(code);
|
||||||
|
console.log('Token obtained successfully');
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const userInfo = await getMyOsuInfo(tokenData.access_token);
|
||||||
|
console.log('User info obtained:', userInfo.username, userInfo.id);
|
||||||
|
|
||||||
|
// 创建更新用户
|
||||||
|
const userSession = await CreateUser({
|
||||||
|
osuid: userInfo.id,
|
||||||
|
username: userInfo.username,
|
||||||
|
avatar_url: userInfo.avatar_url,
|
||||||
|
cover_url: userInfo.cover?.url,
|
||||||
|
country_code: userInfo.country_code,
|
||||||
|
pp: userInfo.statistics?.pp || 0,
|
||||||
|
global_rank: userInfo.statistics?.global_rank || null,
|
||||||
|
country_rank: userInfo.statistics?.country_rank || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('User created/updated successfully:', userSession.username);
|
||||||
|
|
||||||
|
const redirectUrl = new URL('/debug', request.url);
|
||||||
|
redirectUrl.searchParams.set('success', 'true');
|
||||||
|
redirectUrl.searchParams.set('username', userSession.username);
|
||||||
|
redirectUrl.searchParams.set('osuid', userSession.osuid.toString());
|
||||||
|
redirectUrl.searchParams.set('userId', userSession.id.toString());
|
||||||
|
|
||||||
|
const response = NextResponse.redirect(redirectUrl);
|
||||||
|
const userCookieData = {
|
||||||
|
id: userSession.id,
|
||||||
|
osuid: userSession.osuid,
|
||||||
|
username: userSession.username,
|
||||||
|
avatar_url: userSession.avatar_url || '',
|
||||||
|
country_code: userSession.country_code,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置 cookie 7 天
|
||||||
|
response.cookies.set('user_session', JSON.stringify(userCookieData), {
|
||||||
|
httpOnly: process.env.NODE_ENV === 'production',
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 604800, // 7 天
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (apiError) {
|
||||||
|
console.error('API error during OAuth flow:', apiError);
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL('/debug?error=' + encodeURIComponent('Failed to process OAuth: ' + (apiError instanceof Error ? apiError.message : String(apiError))), request.url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Unexpected error in OAuth callback:', error);
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL('/debug?error=' + encodeURIComponent('Unexpected error: ' + (error instanceof Error ? error.message : String(error))), request.url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/api/auth/getAuthUrl/route.ts
Normal file
15
app/api/auth/getAuthUrl/route.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getOsuAuthUrl } from '@/app/lib/osuAuth';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const authUrl = getOsuAuthUrl();
|
||||||
|
return NextResponse.redirect(authUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating OAuth URL:', error);
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL('/debug?error=' + encodeURIComponent('Failed to initialize OAuth: ' + (error instanceof Error ? error.message : String(error))),
|
||||||
|
new URL('/', process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
76
app/api/user/me/route.ts
Normal file
76
app/api/user/me/route.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getUserByOsuId } from '@/app/lib/UserOperation';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 从 cookie 中获取用户会话
|
||||||
|
const userSessionCookie = request.cookies.get('user_session');
|
||||||
|
|
||||||
|
console.log('Cookie received:', userSessionCookie ? 'yes' : 'no');
|
||||||
|
if (userSessionCookie) {
|
||||||
|
console.log('Cookie value length:', userSessionCookie.value.length);
|
||||||
|
console.log('Cookie value first 100 chars:', userSessionCookie.value.substring(0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userSessionCookie || !userSessionCookie.value) {
|
||||||
|
console.log('No user session cookie found');
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Not authenticated' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const userData = JSON.parse(userSessionCookie.value);
|
||||||
|
console.log('Parsed user data:', userData);
|
||||||
|
|
||||||
|
// 从数据库获取完整的用户信息
|
||||||
|
const user = await getUserByOsuId(userData.osuid);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
console.log('User not found in database for osuid:', userData.osuid);
|
||||||
|
// 如果数据库中没有用户,清除 cookie
|
||||||
|
const response = NextResponse.json(
|
||||||
|
{ error: 'User not found in database' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
response.cookies.delete('user_session');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('User found:', user.username);
|
||||||
|
return NextResponse.json(user);
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('Error parsing user session cookie:', parseError);
|
||||||
|
console.error('Raw cookie value:', userSessionCookie.value);
|
||||||
|
// 清除无效的 cookie
|
||||||
|
const response = NextResponse.json(
|
||||||
|
{ error: 'Invalid session' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
response.cookies.delete('user_session');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting current user:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 清除用户会话 cookie
|
||||||
|
const response = NextResponse.json({ success: true });
|
||||||
|
response.cookies.delete('user_session');
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error logging out:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
app/api/user/search/route.ts
Normal file
51
app/api/user/search/route.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { getUserByOsuId, getUserByUsername } from '@/app/lib/UserOperation';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const searchParams = request.nextUrl.searchParams;
|
||||||
|
const type = searchParams.get('type'); // 'osuid' or 'username'
|
||||||
|
const value = searchParams.get('value');
|
||||||
|
|
||||||
|
if (!type || !value) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Missing type or value parameter' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user;
|
||||||
|
if (type === 'osuid') {
|
||||||
|
const osuid = parseInt(value);
|
||||||
|
if (isNaN(osuid)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid osuid' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
user = await getUserByOsuId(osuid);
|
||||||
|
} else if (type === 'username') {
|
||||||
|
user = await getUserByUsername(value);
|
||||||
|
} else {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid type parameter. Use "osuid" or "username"' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'User not found' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(user);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching user:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/api/user/update/route.ts
Normal file
25
app/api/user/update/route.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { updateProfile } from '@/app/lib/UserOperation';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { osuid, ...updateData } = body;
|
||||||
|
|
||||||
|
if (!osuid) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Missing osuid parameter' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await updateProfile(osuid, updateData);
|
||||||
|
return NextResponse.json(updatedUser);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating user:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
import { prisma } from '@/app/lib/PrismaClient';
|
|
||||||
|
|
||||||
// GET: 获取配置
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
|
||||||
const config = await prisma.tournamentConfig.findUnique({
|
|
||||||
where: { id: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
return NextResponse.json(config);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取比赛配置失败:', error);
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '获取配置失败' },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
473
app/debug/components/RegisterUserCard.tsx
Normal file
473
app/debug/components/RegisterUserCard.tsx
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Season } from '@/app/generated/prisma/enums';
|
||||||
|
|
||||||
|
interface UserInfo {
|
||||||
|
id: number;
|
||||||
|
osuid: number;
|
||||||
|
username: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
cover_url?: string;
|
||||||
|
country_code: string;
|
||||||
|
pp: number;
|
||||||
|
global_rank: number;
|
||||||
|
country_rank: number;
|
||||||
|
seasonal: Season;
|
||||||
|
userState: string;
|
||||||
|
approved: boolean;
|
||||||
|
seed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RegisterUserCard() {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
|
||||||
|
const [searchType, setSearchType] = useState<'osuid' | 'username'>('osuid');
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
const [updateData, setUpdateData] = useState({
|
||||||
|
username: '',
|
||||||
|
avatar_url: '',
|
||||||
|
cover_url: '',
|
||||||
|
country_code: '',
|
||||||
|
pp: 0,
|
||||||
|
global_rank: 0,
|
||||||
|
country_rank: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查URL参数,处理OAuth回调结果,并检查当前登录状态
|
||||||
|
useEffect(() => {
|
||||||
|
const checkCurrentUser = async () => {
|
||||||
|
console.log('Checking current user session...');
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/user/me', {
|
||||||
|
credentials: 'include', // 确保发送 cookie
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('API response status:', response.status);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const user = await response.json();
|
||||||
|
console.log('User found:', user.username);
|
||||||
|
|
||||||
|
setUserInfo({
|
||||||
|
id: user.id,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url,
|
||||||
|
cover_url: user.cover_url,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userState: user.userState,
|
||||||
|
approved: user.approved === 1,
|
||||||
|
seed: user.seed,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUpdateData({
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || '',
|
||||||
|
cover_url: user.cover_url || '',
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('User not authenticated or API error:', response.status);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// 忽略错误,用户可能未登录
|
||||||
|
console.log('Error checking user session:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查URL参数,处理OAuth回调结果
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const successParam = params.get('success');
|
||||||
|
const usernameParam = params.get('username');
|
||||||
|
const osuidParam = params.get('osuid');
|
||||||
|
const errorParam = params.get('error');
|
||||||
|
|
||||||
|
if (successParam === 'true' && usernameParam && osuidParam) {
|
||||||
|
setSuccess(`用户 ${usernameParam} (osuid: ${osuidParam}) 注册/登录成功!`);
|
||||||
|
// 清空URL参数
|
||||||
|
const newUrl = window.location.pathname;
|
||||||
|
window.history.replaceState({}, '', newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorParam) {
|
||||||
|
setError(decodeURIComponent(errorParam));
|
||||||
|
// 清空URL参数
|
||||||
|
const newUrl = window.location.pathname;
|
||||||
|
window.history.replaceState({}, '', newUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkCurrentUser();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOsuLogin = () => {
|
||||||
|
window.location.href = '/api/auth/getAuthUrl';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchUser = async () => {
|
||||||
|
if (!searchValue.trim()) {
|
||||||
|
setError('请输入搜索值');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user/search?type=${searchType}&value=${encodeURIComponent(searchValue)}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || `搜索失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await response.json();
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
setUserInfo({
|
||||||
|
id: user.id,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url,
|
||||||
|
cover_url: user.cover_url,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userState: user.userState,
|
||||||
|
approved: user.approved === 1,
|
||||||
|
seed: user.seed,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUpdateData({
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || '',
|
||||||
|
cover_url: user.cover_url || '',
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSuccess(`找到用户: ${user.username} (ID: ${user.id})`);
|
||||||
|
} else {
|
||||||
|
setError('未找到用户');
|
||||||
|
setUserInfo(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('搜索用户失败: ' + (err instanceof Error ? err.message : String(err)));
|
||||||
|
setUserInfo(null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateProfile = async () => {
|
||||||
|
if (!userInfo) {
|
||||||
|
setError('没有用户信息可更新');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatePayload: {
|
||||||
|
username?: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
cover_url?: string;
|
||||||
|
country_code?: string;
|
||||||
|
pp?: number;
|
||||||
|
global_rank?: number;
|
||||||
|
country_rank?: number;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
// 只添加有变化的字段
|
||||||
|
if (updateData.username !== userInfo.username) updatePayload.username = updateData.username;
|
||||||
|
if (updateData.avatar_url !== userInfo.avatar_url) updatePayload.avatar_url = updateData.avatar_url;
|
||||||
|
if (updateData.cover_url !== userInfo.cover_url) updatePayload.cover_url = updateData.cover_url;
|
||||||
|
if (updateData.country_code !== userInfo.country_code) updatePayload.country_code = updateData.country_code;
|
||||||
|
if (updateData.pp !== userInfo.pp) updatePayload.pp = updateData.pp;
|
||||||
|
if (updateData.global_rank !== userInfo.global_rank) updatePayload.global_rank = updateData.global_rank;
|
||||||
|
if (updateData.country_rank !== userInfo.country_rank) updatePayload.country_rank = updateData.country_rank;
|
||||||
|
|
||||||
|
// 如果没有变化,直接返回
|
||||||
|
if (Object.keys(updatePayload).length === 0) {
|
||||||
|
setSuccess('没有需要更新的信息');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/user/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
osuid: userInfo.osuid,
|
||||||
|
...updatePayload,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || `更新失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await response.json();
|
||||||
|
|
||||||
|
setUserInfo({
|
||||||
|
id: updatedUser.id,
|
||||||
|
osuid: updatedUser.osuid,
|
||||||
|
username: updatedUser.username,
|
||||||
|
avatar_url: updatedUser.avatar_url,
|
||||||
|
cover_url: updatedUser.cover_url,
|
||||||
|
country_code: updatedUser.country_code,
|
||||||
|
pp: updatedUser.pp,
|
||||||
|
global_rank: updatedUser.global_rank,
|
||||||
|
country_rank: updatedUser.country_rank,
|
||||||
|
seasonal: updatedUser.seasonal,
|
||||||
|
userState: updatedUser.userState,
|
||||||
|
approved: updatedUser.approved === 1,
|
||||||
|
seed: updatedUser.seed,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSuccess('用户信息更新成功!');
|
||||||
|
} catch (err) {
|
||||||
|
setError('更新用户信息失败: ' + (err instanceof Error ? err.message : String(err)));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
// 调用 API 清除 cookie
|
||||||
|
const response = await fetch('/api/user/me', {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setUserInfo(null);
|
||||||
|
setUpdateData({
|
||||||
|
username: '',
|
||||||
|
avatar_url: '',
|
||||||
|
cover_url: '',
|
||||||
|
country_code: '',
|
||||||
|
pp: 0,
|
||||||
|
global_rank: 0,
|
||||||
|
country_rank: 0,
|
||||||
|
});
|
||||||
|
setSuccess('已退出登录');
|
||||||
|
setError(null);
|
||||||
|
} else {
|
||||||
|
setError('退出登录失败');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('退出登录失败: ' + (err instanceof Error ? err.message : String(err)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||||
|
<h2 className="text-2xl font-bold mb-4">用户注册与测试</h2>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<div className="mb-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded">
|
||||||
|
{success}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-lg font-semibold mb-2">osu! OAuth 登录</h3>
|
||||||
|
<p className="text-gray-600 mb-4">使用 osu! 账号登录并自动注册用户</p>
|
||||||
|
<button
|
||||||
|
onClick={handleOsuLogin}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-4 py-2 bg-pink-600 text-white rounded hover:bg-pink-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? '处理中...' : '使用 osu! 账号登录'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-lg font-semibold mb-2">搜索用户</h3>
|
||||||
|
<div className="flex flex-col md:flex-row gap-2 mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={searchType === 'osuid'}
|
||||||
|
onChange={() => setSearchType('osuid')}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
osuid
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={searchType === 'username'}
|
||||||
|
onChange={() => setSearchType('username')}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
用户名
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
placeholder={searchType === 'osuid' ? '输入 osuid' : '输入用户名'}
|
||||||
|
className="flex-grow px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSearchUser}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? '搜索中...' : '搜索用户'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userInfo && (
|
||||||
|
<div className="border-t pt-6">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h3 className="text-lg font-semibold">用户信息</h3>
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
退出登录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<p><strong>ID:</strong> {userInfo.id}</p>
|
||||||
|
<p><strong>osuid:</strong> {userInfo.osuid}</p>
|
||||||
|
<p><strong>用户名:</strong> {userInfo.username}</p>
|
||||||
|
<p><strong>地区:</strong> {userInfo.country_code}</p>
|
||||||
|
<p><strong>PP:</strong> {userInfo.pp.toFixed(2)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p><strong>全球排名:</strong> {userInfo.global_rank}</p>
|
||||||
|
<p><strong>地区排名:</strong> {userInfo.country_rank}</p>
|
||||||
|
<p><strong>赛季:</strong> {userInfo.seasonal}</p>
|
||||||
|
<p><strong>状态:</strong> {userInfo.userState}</p>
|
||||||
|
<p><strong>已批准:</strong> {userInfo.approved ? '是' : '否'}</p>
|
||||||
|
<p><strong>种子:</strong> {userInfo.seed}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{userInfo.avatar_url && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="font-semibold mb-2">头像:</p>
|
||||||
|
<Image src={userInfo.avatar_url}
|
||||||
|
alt="用户头像"
|
||||||
|
className="w-24 h-24 rounded-full"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<h4 className="text-md font-semibold mb-3">更新用户信息</h4>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">用户名</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={updateData.username}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, username: e.target.value})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">地区代码</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={updateData.country_code}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, country_code: e.target.value})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">PP</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={updateData.pp}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, pp: parseFloat(e.target.value) || 0})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">全球排名</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={updateData.global_rank}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, global_rank: parseInt(e.target.value) || 0})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">地区排名</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={updateData.country_rank}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, country_rank: parseInt(e.target.value) || 0})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">头像URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={updateData.avatar_url}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, avatar_url: e.target.value})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">封面URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={updateData.cover_url}
|
||||||
|
onChange={(e) => setUpdateData({...updateData, cover_url: e.target.value})}
|
||||||
|
className="w-full px-3 py-2 border rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleUpdateProfile}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? '更新中...' : '更新用户信息'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import TournamentConfigCard from '@/app/debug/components/TournamentConfigCard';
|
import TournamentConfigCard from '@/app/debug/components/TournamentConfigCard';
|
||||||
|
import RegisterUserCard from '@/app/debug/components/RegisterUserCard';
|
||||||
|
|
||||||
export default function DebugPage() {
|
export default function DebugPage() {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6">
|
<div className="container mx-auto p-6">
|
||||||
<h1 className="text-3xl font-bold mb-6">调试页面</h1>
|
<h1 className="text-3xl font-bold mb-6">调试页面</h1>
|
||||||
|
|
||||||
|
<RegisterUserCard />
|
||||||
<TournamentConfigCard />
|
<TournamentConfigCard />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { PrismaClient } from '@/app/generated/prisma/client'
|
|||||||
|
|
||||||
import { PrismaPg } from "@prisma/adapter-pg";
|
import { PrismaPg } from "@prisma/adapter-pg";
|
||||||
|
|
||||||
const connectionString = process.env.DATABASE_URL;
|
const connectionString = process.env.DATABASE_CLIENT_URL;
|
||||||
const adapter = new PrismaPg({ connectionString });
|
const adapter = new PrismaPg({ connectionString });
|
||||||
|
|
||||||
export const prisma = new PrismaClient({
|
export const prisma = new PrismaClient({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { UserState } from "@/app/generated/prisma/enums";
|
import { UserState,Season } from "@/app/generated/prisma/enums";
|
||||||
import { MultiplayerSoloRoom, Team } from "@/app/generated/prisma/client";
|
import { MultiplayerSoloRoom, Team } from "@/app/generated/prisma/client";
|
||||||
|
import { prisma } from "./PrismaClient";
|
||||||
|
|
||||||
export interface UserSession {
|
export interface UserSession {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -14,7 +15,7 @@ export interface UserSession {
|
|||||||
country_rank: number;
|
country_rank: number;
|
||||||
approved: number;
|
approved: number;
|
||||||
seed: number;
|
seed: number;
|
||||||
seasonal: number;
|
seasonal: Season;
|
||||||
userGroups: number[];
|
userGroups: number[];
|
||||||
SoloRedPlayer?: MultiplayerSoloRoom[];
|
SoloRedPlayer?: MultiplayerSoloRoom[];
|
||||||
SoloBluePlayer?: MultiplayerSoloRoom[];
|
SoloBluePlayer?: MultiplayerSoloRoom[];
|
||||||
@@ -23,6 +24,323 @@ export interface UserSession {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function CreateUser(user:UserSession) {
|
export async function CreateUser(userData: {
|
||||||
|
osuid: number;
|
||||||
|
username: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
cover_url?: string;
|
||||||
|
country_code: string;
|
||||||
|
pp: number;
|
||||||
|
global_rank: number | null;
|
||||||
|
country_rank: number | null;
|
||||||
|
}): Promise<UserSession> {
|
||||||
|
try {
|
||||||
|
// 获取当前配置
|
||||||
|
const config = await prisma.tournamentConfig.findUnique({
|
||||||
|
where: { id: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentSeasonal = config?.current_seasonal || Season.S1;
|
||||||
|
|
||||||
|
const user = await prisma.user.upsert({
|
||||||
|
where: { osuid: userData.osuid },
|
||||||
|
update: {
|
||||||
|
username: userData.username,
|
||||||
|
avatar_url: userData.avatar_url,
|
||||||
|
cover_url: userData.cover_url,
|
||||||
|
country_code: userData.country_code,
|
||||||
|
pp: userData.pp,
|
||||||
|
global_rank: userData.global_rank || 0,
|
||||||
|
country_rank: userData.country_rank || 0,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
osuid: userData.osuid,
|
||||||
|
username: userData.username,
|
||||||
|
avatar_url: userData.avatar_url,
|
||||||
|
cover_url: userData.cover_url,
|
||||||
|
country_code: userData.country_code,
|
||||||
|
pp: userData.pp,
|
||||||
|
global_rank: userData.global_rank || 0,
|
||||||
|
country_rank: userData.country_rank || 0,
|
||||||
|
userState: UserState.ACTIVE,
|
||||||
|
approved: false,
|
||||||
|
seed: 0,
|
||||||
|
seasonal: currentSeasonal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
userState: user.userState,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || undefined,
|
||||||
|
cover_url: user.cover_url || undefined,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
approved: user.approved ? 1 : 0,
|
||||||
|
seed: user.seed,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userGroups: [],
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating/updating user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserByOsuId(osuid: number): Promise<UserSession | null> {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { osuid },
|
||||||
|
include: {
|
||||||
|
userGroups: true,
|
||||||
|
SoloRedPlayer: true,
|
||||||
|
SoloBluePlayer: true,
|
||||||
|
teams: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
userState: user.userState,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || undefined,
|
||||||
|
cover_url: user.cover_url || undefined,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
approved: user.approved ? 1 : 0,
|
||||||
|
seed: user.seed,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userGroups: user.userGroups.map(group => group.id),
|
||||||
|
SoloRedPlayer: user.SoloRedPlayer,
|
||||||
|
SoloBluePlayer: user.SoloBluePlayer,
|
||||||
|
teams: user.teams,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user by osuid:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserByUsername(username: string): Promise<UserSession | null> {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { username },
|
||||||
|
include: {
|
||||||
|
userGroups: true,
|
||||||
|
SoloRedPlayer: true,
|
||||||
|
SoloBluePlayer: true,
|
||||||
|
teams: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
userState: user.userState,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || undefined,
|
||||||
|
cover_url: user.cover_url || undefined,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
approved: user.approved ? 1 : 0,
|
||||||
|
seed: user.seed,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userGroups: user.userGroups.map(group => group.id),
|
||||||
|
SoloRedPlayer: user.SoloRedPlayer,
|
||||||
|
SoloBluePlayer: user.SoloBluePlayer,
|
||||||
|
teams: user.teams,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user by username:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateProfile(osuid: number, updateData: {
|
||||||
|
username?: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
cover_url?: string;
|
||||||
|
country_code?: string;
|
||||||
|
pp?: number;
|
||||||
|
global_rank?: number;
|
||||||
|
country_rank?: number;
|
||||||
|
userState?: UserState;
|
||||||
|
approved?: boolean;
|
||||||
|
seed?: number;
|
||||||
|
seasonal?: Season;
|
||||||
|
}): Promise<UserSession> {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.update({
|
||||||
|
where: { osuid },
|
||||||
|
data: {
|
||||||
|
...updateData,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
userGroups: true,
|
||||||
|
SoloRedPlayer: true,
|
||||||
|
SoloBluePlayer: true,
|
||||||
|
teams: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
userState: user.userState,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || undefined,
|
||||||
|
cover_url: user.cover_url || undefined,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
approved: user.approved ? 1 : 0,
|
||||||
|
seed: user.seed,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userGroups: user.userGroups.map(group => group.id),
|
||||||
|
SoloRedPlayer: user.SoloRedPlayer,
|
||||||
|
SoloBluePlayer: user.SoloBluePlayer,
|
||||||
|
teams: user.teams,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating user profile:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addUserToGroup(userId: number, groupId: number): Promise<UserSession> {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
userGroups: {
|
||||||
|
connect: { id: groupId },
|
||||||
|
},
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
userGroups: true,
|
||||||
|
SoloRedPlayer: true,
|
||||||
|
SoloBluePlayer: true,
|
||||||
|
teams: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
userState: user.userState,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || undefined,
|
||||||
|
cover_url: user.cover_url || undefined,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
approved: user.approved ? 1 : 0,
|
||||||
|
seed: user.seed,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userGroups: user.userGroups.map(group => group.id),
|
||||||
|
SoloRedPlayer: user.SoloRedPlayer,
|
||||||
|
SoloBluePlayer: user.SoloBluePlayer,
|
||||||
|
teams: user.teams,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding user to group:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeUserFromGroup(userId: number, groupId: number): Promise<UserSession> {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: {
|
||||||
|
userGroups: {
|
||||||
|
disconnect: { id: groupId },
|
||||||
|
},
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
userGroups: true,
|
||||||
|
SoloRedPlayer: true,
|
||||||
|
SoloBluePlayer: true,
|
||||||
|
teams: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
userState: user.userState,
|
||||||
|
osuid: user.osuid,
|
||||||
|
username: user.username,
|
||||||
|
avatar_url: user.avatar_url || undefined,
|
||||||
|
cover_url: user.cover_url || undefined,
|
||||||
|
country_code: user.country_code,
|
||||||
|
pp: user.pp,
|
||||||
|
global_rank: user.global_rank,
|
||||||
|
country_rank: user.country_rank,
|
||||||
|
approved: user.approved ? 1 : 0,
|
||||||
|
seed: user.seed,
|
||||||
|
seasonal: user.seasonal,
|
||||||
|
userGroups: user.userGroups.map(group => group.id),
|
||||||
|
SoloRedPlayer: user.SoloRedPlayer,
|
||||||
|
SoloBluePlayer: user.SoloBluePlayer,
|
||||||
|
teams: user.teams,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing user from group:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserGroups(userId: number): Promise<number[]> {
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
include: {
|
||||||
|
userGroups: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.userGroups.map(group => group.id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user groups:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
// 这些环境变量只在服务器端可用
|
||||||
const OSU_CLIENT_ID = process.env.OSU_CLIENT_ID || '';
|
const OSU_CLIENT_ID = process.env.OSU_CLIENT_ID || '';
|
||||||
const OSU_CLIENT_SECRET = process.env.OSU_CLIENT_SECRET || '';
|
const OSU_CLIENT_SECRET = process.env.OSU_CLIENT_SECRET || '';
|
||||||
const OSU_REDIRECT_URI = process.env.OSU_REDIRECT_URI || 'http://localhost:3000/auth/osu/callback';
|
const OSU_REDIRECT_URI = process.env.OSU_REDIRECT_URI || 'http://localhost:3000/api/auth/callback/osu';
|
||||||
|
|
||||||
export function getOsuAuthUrl() {
|
export function getOsuAuthUrl() {
|
||||||
if (!OSU_CLIENT_ID || !OSU_CLIENT_SECRET) {
|
if (!OSU_CLIENT_ID || !OSU_CLIENT_SECRET) {
|
||||||
|
|||||||
@@ -1,7 +1,25 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'a.ppy.sh',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'assets.ppy.sh',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'osu.ppy.sh',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
307
prisma/migrations/20260205015503_init/migration.sql
Normal file
307
prisma/migrations/20260205015503_init/migration.sql
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "BeatmapSelectMod" AS ENUM ('NM', 'HD', 'HR', 'DT', 'LZ', 'TB');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "MultiplayerRoomType" AS ENUM ('SOLO', 'TEAM_VS');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "TeamColor" AS ENUM ('blue_team', 'red_team');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "TeamState" AS ENUM ('ACTIVE', 'UNAPPROVED', 'APPROVED', 'BANNED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "UserState" AS ENUM ('ACTIVE', 'REGISTERED', 'ABANDONED', 'BANNED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "UserGroupType" AS ENUM ('HOST', 'ADMIN', 'POOLER', 'STREAMER', 'TESTER', 'GFX', 'SHEETER', 'COMMENTATOR', 'MAPPER', 'REFEREE', 'PLAYER');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Season" AS ENUM ('S1', 'S2');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Category" AS ENUM ('QUA', 'RO16', 'QF', 'SF', 'F', 'GF');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Beatmap" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"beatmap_id" INTEGER NOT NULL,
|
||||||
|
"beatmapset_id" INTEGER NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"title_unicode" TEXT NOT NULL,
|
||||||
|
"artist" TEXT NOT NULL,
|
||||||
|
"artist_unicode" TEXT NOT NULL,
|
||||||
|
"creator" TEXT NOT NULL,
|
||||||
|
"cover_url" TEXT NOT NULL,
|
||||||
|
"version" TEXT NOT NULL,
|
||||||
|
"ar" DOUBLE PRECISION NOT NULL,
|
||||||
|
"od" DOUBLE PRECISION NOT NULL,
|
||||||
|
"cs" DOUBLE PRECISION NOT NULL,
|
||||||
|
"hp" DOUBLE PRECISION NOT NULL,
|
||||||
|
"bpm" DOUBLE PRECISION NOT NULL,
|
||||||
|
"length" INTEGER NOT NULL,
|
||||||
|
"max_combo" INTEGER NOT NULL,
|
||||||
|
"star_rating" DOUBLE PRECISION NOT NULL,
|
||||||
|
"mod" JSONB NOT NULL DEFAULT '[]',
|
||||||
|
"selectMod" "BeatmapSelectMod" NOT NULL DEFAULT 'NM',
|
||||||
|
"selectModSlot" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"selectByosuId" INTEGER NOT NULL,
|
||||||
|
"selectNote" TEXT NOT NULL DEFAULT '',
|
||||||
|
"approved" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"needTest" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isCustom" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isOriginal" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"season" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"category" "Category" NOT NULL DEFAULT 'QUA',
|
||||||
|
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Beatmap_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "BeatmapComment" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"beatmap_id" INTEGER NOT NULL,
|
||||||
|
"comment" TEXT NOT NULL DEFAULT '',
|
||||||
|
"osuid" INTEGER NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "BeatmapComment_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Message" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"sender_id" INTEGER NOT NULL,
|
||||||
|
"receiver_id" INTEGER NOT NULL,
|
||||||
|
"room_id" INTEGER,
|
||||||
|
"isRead" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDeleted" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isSystemMessage" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isAccepted" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Message_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MultiplayerRoom" (
|
||||||
|
"room_id" SERIAL NOT NULL,
|
||||||
|
"type" "MultiplayerRoomType" NOT NULL DEFAULT 'SOLO',
|
||||||
|
"season" "Season" NOT NULL DEFAULT 'S1',
|
||||||
|
"category" "Category" NOT NULL DEFAULT 'QUA',
|
||||||
|
"multiplayerSoloRoomRoom_id" INTEGER,
|
||||||
|
"multiplayerTeamvsRoomRoom_id" INTEGER,
|
||||||
|
"score_red" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"score_blue" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"start_time" TIMESTAMP(3) NOT NULL,
|
||||||
|
"end_time" TIMESTAMP(3) NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "MultiplayerRoom_pkey" PRIMARY KEY ("room_id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MultiplayerSoloRoom" (
|
||||||
|
"room_id" SERIAL NOT NULL,
|
||||||
|
"player_red_id" INTEGER NOT NULL,
|
||||||
|
"player_blue_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "MultiplayerSoloRoom_pkey" PRIMARY KEY ("room_id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MultiplayerTeamvsRoom" (
|
||||||
|
"room_id" SERIAL NOT NULL,
|
||||||
|
"team_red_id" INTEGER NOT NULL,
|
||||||
|
"team_blue_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "MultiplayerTeamvsRoom_pkey" PRIMARY KEY ("room_id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Score" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"beatmap_id" INTEGER NOT NULL,
|
||||||
|
"total_score" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"accuracy" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"max_combo" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"mods" JSONB NOT NULL DEFAULT '[]',
|
||||||
|
"rank" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"pp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"passed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"statistics" JSONB NOT NULL DEFAULT '[]',
|
||||||
|
"ended_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
"saved_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Score_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Team" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"banner_url" TEXT NOT NULL,
|
||||||
|
"teamColor" "TeamColor" NOT NULL,
|
||||||
|
"teamState" "TeamState" NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Team_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"userState" "UserState" NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
"osuid" INTEGER NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"avatar_url" TEXT,
|
||||||
|
"cover_url" TEXT,
|
||||||
|
"country_code" TEXT NOT NULL,
|
||||||
|
"pp" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"global_rank" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"country_rank" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"approved" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"seed" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"seasonal" "Season" NOT NULL DEFAULT 'S1',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserGroup" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"group" "UserGroupType" NOT NULL DEFAULT 'PLAYER',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UserGroup_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TournamentConfig" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"tournament_name" TEXT NOT NULL DEFAULT 'AstarCup',
|
||||||
|
"max_pp_for_registration" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"min_pp_for_registration" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"current_seasonal" "Season" NOT NULL DEFAULT 'S1',
|
||||||
|
"current_category" "Category" NOT NULL DEFAULT 'QUA',
|
||||||
|
"canRegister" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "TournamentConfig_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_TeamToUser" (
|
||||||
|
"A" INTEGER NOT NULL,
|
||||||
|
"B" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_TeamToUser_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_UserToUserGroup" (
|
||||||
|
"A" INTEGER NOT NULL,
|
||||||
|
"B" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_UserToUserGroup_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Beatmap_beatmap_id_key" ON "Beatmap"("beatmap_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Beatmap_beatmapset_id_key" ON "Beatmap"("beatmapset_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "BeatmapComment_beatmap_id_key" ON "BeatmapComment"("beatmap_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "BeatmapComment_osuid_key" ON "BeatmapComment"("osuid");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "MultiplayerSoloRoom_player_red_id_key" ON "MultiplayerSoloRoom"("player_red_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "MultiplayerSoloRoom_player_blue_id_key" ON "MultiplayerSoloRoom"("player_blue_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "MultiplayerTeamvsRoom_team_red_id_key" ON "MultiplayerTeamvsRoom"("team_red_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "MultiplayerTeamvsRoom_team_blue_id_key" ON "MultiplayerTeamvsRoom"("team_blue_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_osuid_key" ON "User"("osuid");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_TeamToUser_B_index" ON "_TeamToUser"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_UserToUserGroup_B_index" ON "_UserToUserGroup"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Beatmap" ADD CONSTRAINT "Beatmap_selectByosuId_fkey" FOREIGN KEY ("selectByosuId") REFERENCES "User"("osuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "BeatmapComment" ADD CONSTRAINT "BeatmapComment_beatmap_id_fkey" FOREIGN KEY ("beatmap_id") REFERENCES "Beatmap"("beatmap_id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "BeatmapComment" ADD CONSTRAINT "BeatmapComment_osuid_fkey" FOREIGN KEY ("osuid") REFERENCES "User"("osuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_sender_id_fkey" FOREIGN KEY ("sender_id") REFERENCES "User"("osuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_receiver_id_fkey" FOREIGN KEY ("receiver_id") REFERENCES "User"("osuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_room_id_fkey" FOREIGN KEY ("room_id") REFERENCES "MultiplayerRoom"("room_id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MultiplayerRoom" ADD CONSTRAINT "MultiplayerRoom_multiplayerSoloRoomRoom_id_fkey" FOREIGN KEY ("multiplayerSoloRoomRoom_id") REFERENCES "MultiplayerSoloRoom"("room_id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MultiplayerRoom" ADD CONSTRAINT "MultiplayerRoom_multiplayerTeamvsRoomRoom_id_fkey" FOREIGN KEY ("multiplayerTeamvsRoomRoom_id") REFERENCES "MultiplayerTeamvsRoom"("room_id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MultiplayerSoloRoom" ADD CONSTRAINT "MultiplayerSoloRoom_player_red_id_fkey" FOREIGN KEY ("player_red_id") REFERENCES "User"("osuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MultiplayerSoloRoom" ADD CONSTRAINT "MultiplayerSoloRoom_player_blue_id_fkey" FOREIGN KEY ("player_blue_id") REFERENCES "User"("osuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MultiplayerTeamvsRoom" ADD CONSTRAINT "MultiplayerTeamvsRoom_team_red_id_fkey" FOREIGN KEY ("team_red_id") REFERENCES "Team"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MultiplayerTeamvsRoom" ADD CONSTRAINT "MultiplayerTeamvsRoom_team_blue_id_fkey" FOREIGN KEY ("team_blue_id") REFERENCES "Team"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_TeamToUser" ADD CONSTRAINT "_TeamToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_TeamToUser" ADD CONSTRAINT "_TeamToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_UserToUserGroup" ADD CONSTRAINT "_UserToUserGroup_A_fkey" FOREIGN KEY ("A") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_UserToUserGroup" ADD CONSTRAINT "_UserToUserGroup_B_fkey" FOREIGN KEY ("B") REFERENCES "UserGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
@@ -11,7 +11,7 @@ model User {
|
|||||||
country_rank Int @default(0)
|
country_rank Int @default(0)
|
||||||
approved Boolean @default(false)
|
approved Boolean @default(false)
|
||||||
seed Int @default(0)
|
seed Int @default(0)
|
||||||
seasonal Int @default(1)
|
seasonal Season @default(S1)
|
||||||
SoloRedPlayer MultiplayerSoloRoom[] @relation("SoloRedPlayer")
|
SoloRedPlayer MultiplayerSoloRoom[] @relation("SoloRedPlayer")
|
||||||
SoloBluePlayer MultiplayerSoloRoom[] @relation("SoloBluePlayer")
|
SoloBluePlayer MultiplayerSoloRoom[] @relation("SoloBluePlayer")
|
||||||
teams Team[]
|
teams Team[]
|
||||||
|
|||||||
Reference in New Issue
Block a user