Connect register API between UI and account service (#332)
This commit is contained in:
parent
71a9251ea8
commit
33fdafffb2
@ -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
63
account/api/api_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user