Connect register API between UI and account service (#332)

This commit is contained in:
shenlan 2025-10-01 00:09:13 +08:00 committed by GitHub
parent 71a9251ea8
commit 33fdafffb2
3 changed files with 143 additions and 5 deletions

View File

@ -44,6 +44,12 @@ func RegisterRoutes(r *gin.Engine) {
v1.POST("/login", h.login)
v1.GET("/session", h.session)
v1.DELETE("/session", h.deleteSession)
auth := r.Group("/api/auth")
auth.POST("/register", h.register)
auth.POST("/login", h.login)
auth.GET("/session", h.session)
auth.DELETE("/session", h.deleteSession)
}
type registerRequest struct {

63
account/api/api_test.go Normal file
View File

@ -0,0 +1,63 @@
package api
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestRegisterEndpointAvailableUnderMultiplePrefixes(t *testing.T) {
gin.SetMode(gin.TestMode)
paths := []string{"/v1/register", "/api/auth/register"}
for _, path := range paths {
router := gin.New()
RegisterRoutes(router)
payload := map[string]string{
"name": "Test User",
"email": "user" + path + "@example.com",
"password": "supersecure",
}
body, err := json.Marshal(payload)
if err != nil {
t.Fatalf("failed to marshal payload: %v", err)
}
req := httptest.NewRequest(http.MethodPost, path, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if rr.Code != http.StatusCreated {
t.Fatalf("%s: expected status %d, got %d, body: %s", path, http.StatusCreated, rr.Code, rr.Body.String())
}
var response struct {
User map[string]any `json:"user"`
}
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Fatalf("%s: failed to decode response: %v", path, err)
}
if response.User == nil {
t.Fatalf("%s: expected user object in response", path)
}
if email, ok := response.User["email"].(string); !ok || email != payload["email"] {
t.Fatalf("%s: expected email %q, got %#v", path, payload["email"], response.User["email"])
}
if _, exists := response.User["password"]; exists {
t.Fatalf("%s: response should not include password field", path)
}
}
}

View File

@ -4,6 +4,13 @@ import { getAccountServiceBaseUrl } from '@lib/serviceConfig'
const ACCOUNT_SERVICE_URL = getAccountServiceBaseUrl()
type RegistrationBody = {
name: string
email: string
password: string
confirmPassword: string
}
async function registerWithAccountService(body: Record<string, string>) {
try {
const response = await fetch(`${ACCOUNT_SERVICE_URL}/v1/register`, {
@ -24,11 +31,20 @@ async function registerWithAccountService(body: Record<string, string>) {
}
export async function POST(request: NextRequest) {
const formData = await request.formData()
const name = String(formData.get('name') ?? '').trim()
const email = String(formData.get('email') ?? '').trim().toLowerCase()
const password = String(formData.get('password') ?? '')
const confirmPassword = String(formData.get('confirmPassword') ?? '')
let fields: RegistrationBody
try {
fields = await extractRegistrationFields(request)
} catch (error) {
console.error('Failed to parse registration payload', error)
const redirectURL = new URL('/register', request.url)
redirectURL.searchParams.set('error', 'invalid_request_payload')
return NextResponse.redirect(redirectURL, { status: 303 })
}
const name = fields.name.trim()
const email = fields.email.trim().toLowerCase()
const password = fields.password
const confirmPassword = fields.confirmPassword
if (!email || !password) {
const redirectURL = new URL('/register', request.url)
@ -54,3 +70,56 @@ export async function POST(request: NextRequest) {
redirectURL.searchParams.set('registered', '1')
return NextResponse.redirect(redirectURL, { status: 303 })
}
function ensureString(value: unknown): string {
return typeof value === 'string' ? value : ''
}
async function extractRegistrationFields(request: NextRequest): Promise<RegistrationBody> {
const contentType = request.headers.get('content-type')?.toLowerCase() ?? ''
if (contentType.includes('application/json')) {
const body = await request.json().catch((error) => {
console.error('Failed to decode JSON body', error)
throw error
})
return {
name: ensureString(body?.name ?? ''),
email: ensureString(body?.email ?? ''),
password: ensureString(body?.password ?? ''),
confirmPassword: ensureString(body?.confirmPassword ?? body?.password ?? ''),
}
}
if (contentType.includes('application/x-www-form-urlencoded')) {
const text = await request.text()
const params = new URLSearchParams(text)
return {
name: ensureString(params.get('name')),
email: ensureString(params.get('email')),
password: ensureString(params.get('password')),
confirmPassword: ensureString(params.get('confirmPassword')),
}
}
const formData = await request.formData()
const read = (key: string) => ensureString(formData.get(key))
return {
name: read('name'),
email: read('email'),
password: read('password'),
confirmPassword: read('confirmPassword'),
}
}
export function GET() {
return NextResponse.json(
{ error: 'method_not_allowed' },
{
status: 405,
headers: {
Allow: 'POST',
},
},
)
}