Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,14 @@ class UserCreateSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = ["username", "password", "password_retype"]
fields = [
"username",
"email",
"first_name",
"last_name",
"password",
"password_retype",
]

def validate(self, attrs):
password_retype = attrs.pop("password_retype")
Expand All @@ -107,16 +114,22 @@ def validate(self, attrs):
def create(self, validated_data):
with transaction.atomic():
user = User.objects.create_user(**validated_data)

# By default newly registered accounts are inactive.
user.is_active = False
# 新注册的用户默认为活跃状态
user.is_active = True
user.save(update_fields=["is_active"])

return user


class UserCreateErrorSerializer(serializers.Serializer):
username = serializers.ListSerializer(child=serializers.CharField(), required=False)
email = serializers.ListSerializer(child=serializers.CharField(), required=False)
first_name = serializers.ListSerializer(
child=serializers.CharField(), required=False
)
last_name = serializers.ListSerializer(
child=serializers.CharField(), required=False
)
password = serializers.ListSerializer(child=serializers.CharField(), required=False)
password_retype = serializers.ListSerializer(
child=serializers.CharField(), required=False
Expand Down
37 changes: 37 additions & 0 deletions backend/api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"corsheaders",
"rest_framework",
"rest_framework_simplejwt",
"drf_spectacular",
Expand All @@ -45,6 +46,7 @@
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
Expand Down Expand Up @@ -169,3 +171,38 @@
],
},
}

######################################################################
# CORS
######################################################################
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://localhost:3001",
"http://127.0.0.1:3000",
"http://127.0.0.1:3001",
]

CORS_ALLOW_CREDENTIALS = True

# 允许的 HTTP 方法
CORS_ALLOW_METHODS = [
"DELETE",
"GET",
"OPTIONS",
"PATCH",
"POST",
"PUT",
]

# 允许的 HTTP 头
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
]
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies = [
"djangorestframework-simplejwt>=5.3",
"drf-spectacular>=0.28",
"django-unfold>=0.43.0",
"django-cors-headers>=4.6.0",
]

[dependency-groups]
Expand Down
226 changes: 121 additions & 105 deletions backend/uv.lock

Large diffs are not rendered by default.

46 changes: 30 additions & 16 deletions frontend/apps/web/actions/change-password-action.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
'use server'

import { getApiClient } from '@/lib/api'
import { apiClient } from '@/lib/api-client'
import { authOptions } from '@/lib/auth'
import type { changePasswordFormSchema } from '@/lib/validation'
import { ApiError, type UserChangePasswordError } from '@frontend/types/api'
import { getServerSession } from 'next-auth'
import type { z } from 'zod'

export type ChangePasswordFormSchema = z.infer<typeof changePasswordFormSchema>

export async function changePasswordAction(
data: ChangePasswordFormSchema
): Promise<UserChangePasswordError | boolean> {
const session = await getServerSession(authOptions)

): Promise<true | { [key: string]: string[] }> {
try {
const apiClient = await getApiClient(session)
// 获取当前用户的会话以获取 token
const session = await getServerSession(authOptions)

if (!session?.accessToken) {
return {
password: ['Authentication required']
}
}

await apiClient.users.usersChangePasswordCreate({
password: data.password,
password_new: data.passwordNew,
password_retype: data.passwordRetype
})
// 调用 API 修改密码,传递 token
await apiClient.post(
'/api/users/change-password/',
{
password: data.password,
password_new: data.passwordNew,
password_retype: data.passwordRetype
},
{
token: session.accessToken
}
)

return true
} catch (error) {
if (error instanceof ApiError) {
return error.body as UserChangePasswordError
} catch (error: any) {
// 处理 API 错误
if (error.data && typeof error.data === 'object') {
return error.data
}
}

return false
return {
password: ['Failed to change password']
}
}
}
28 changes: 14 additions & 14 deletions frontend/apps/web/actions/delete-account-action.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use server'

import { getApiClient } from '@/lib/api'
import { apiClient } from '@/lib/api-client'
import { authOptions } from '@/lib/auth'
import type { deleteAccountFormSchema } from '@/lib/validation'
import { ApiError } from '@frontend/types/api'
import { getServerSession } from 'next-auth'
import type { z } from 'zod'

Expand All @@ -12,21 +11,22 @@ export type DeleteAccountFormSchema = z.infer<typeof deleteAccountFormSchema>
export async function deleteAccountAction(
data: DeleteAccountFormSchema
): Promise<boolean> {
const session = await getServerSession(authOptions)

try {
const apiClient = await getApiClient(session)

if (session !== null) {
await apiClient.users.usersDeleteAccountDestroy()
// 获取当前用户的会话以获取 token
const session = await getServerSession(authOptions)

return true
}
} catch (error) {
if (error instanceof ApiError) {
if (!session?.accessToken) {
return false
}
}

return false
// 调用 API 删除账户,传递 token
await apiClient.delete('/api/users/delete-account/', {
token: session.accessToken
})

return true
} catch (error: any) {
console.error('Failed to delete account:', error)
return false
}
}
33 changes: 0 additions & 33 deletions frontend/apps/web/actions/profile-action.ts

This file was deleted.

30 changes: 0 additions & 30 deletions frontend/apps/web/actions/register-action.ts

This file was deleted.

16 changes: 2 additions & 14 deletions frontend/apps/web/app/(account)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import { profileAction } from '@/actions/profile-action'
import { ProfileForm } from '@/components/forms/profile-form'
import { getApiClient } from '@/lib/api'
import { authOptions } from '@/lib/auth'
import type { Metadata } from 'next'
import { getServerSession } from 'next-auth'

export const metadata: Metadata = {
title: 'Profile - Turbo'
}

export default async function Profile() {
const session = await getServerSession(authOptions)
const apiClient = await getApiClient(session)

return (
<ProfileForm
currentUser={apiClient.users.usersMeRetrieve()}
onSubmitHandler={profileAction}
/>
)
export default function Profile() {
return <ProfileForm />
}
3 changes: 1 addition & 2 deletions frontend/apps/web/app/(auth)/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { registerAction } from '@/actions/register-action'
import { RegisterForm } from '@/components/forms/register-form'
import type { Metadata } from 'next'

Expand All @@ -7,5 +6,5 @@ export const metadata: Metadata = {
}

export default function Register() {
return <RegisterForm onSubmitHandler={registerAction} />
return <RegisterForm />
}
19 changes: 13 additions & 6 deletions frontend/apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AuthProvider } from '@/providers/auth-provider'
import { QueryProvider } from '@/providers/query-provider'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { twMerge } from 'tailwind-merge'
Expand All @@ -13,7 +14,9 @@ export const metadata: Metadata = {

export default function RootLayout({
children
}: { children: React.ReactNode }) {
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body
Expand All @@ -22,11 +25,15 @@ export default function RootLayout({
inter.className
)}
>
<AuthProvider>
<div className="px-6">
<div className="container mx-auto my-12 max-w-6xl">{children}</div>
</div>
</AuthProvider>
<QueryProvider>
<AuthProvider>
<div className="px-6">
<div className="container mx-auto my-12 max-w-6xl">
{children}
</div>
</div>
</AuthProvider>
</QueryProvider>
</body>
</html>
)
Expand Down
Loading