@@ -8,49 +8,104 @@ import { sendWelcomeEmail } from '../utils/notifications'
88const signupSchema = z . object ( {
99 email : z . string ( ) . email ( 'Invalid email address' ) ,
1010 password : z . string ( ) . min ( 8 , 'Password must be at least 8 characters' ) ,
11- firstName : z . string ( ) . optional ( ) ,
12- lastName : z . string ( ) . optional ( ) ,
11+ firstName : z . string ( ) . min ( 1 , 'First name is required' ) ,
12+ lastName : z . string ( ) . min ( 1 , 'Last name is required' ) ,
13+ whatsappNumber : z . string ( ) . optional ( ) ,
1314} )
1415
1516export const signup = asyncHandler ( async ( req : Request , res : Response ) => {
1617 let session
1718
1819 try {
19- const { email, password, firstName, lastName } = signupSchema . parse (
20- req . body ,
21- )
20+ const { email, password, firstName, lastName, whatsappNumber } =
21+ signupSchema . parse ( req . body )
2222
2323 session = getSession ( )
2424
25- // Check if user already exists (check both Member and User labels)
26- const existingUser = await session . run (
27- `MATCH (u:User {email: $email}) RETURN u LIMIT 1` ,
25+ // 1. Check for inactive member first (they can be reactivated)
26+ const inactiveMemberResult = await session . run (
27+ `MATCH (m:Member {email: $email})
28+ WHERE m.markedInactive = true
29+ RETURN m.id as id, m.email as email, m.firstName as firstName, m.lastName as lastName
30+ LIMIT 1` ,
31+ { email } ,
32+ )
33+
34+ if ( inactiveMemberResult . records . length > 0 ) {
35+ const inactiveMember = inactiveMemberResult . records [ 0 ]
36+ const memberId = inactiveMember . get ( 'id' )
37+ const memberFirstName = inactiveMember . get ( 'firstName' )
38+ const memberLastName = inactiveMember . get ( 'lastName' )
39+
40+ throw new ApiError (
41+ 409 ,
42+ `An inactive account exists for ${ memberFirstName } ${ memberLastName } . Please contact support to reactivate your account.` ,
43+ )
44+ }
45+
46+ // 2. Check for duplicate email (active members/users)
47+ const emailCheckResult = await session . run (
48+ `MATCH (u)
49+ WHERE (u:User OR u:Member) AND u.email = $email
50+ RETURN u.id as id, u.email as email, u.firstName as firstName, u.lastName as lastName
51+ LIMIT 1` ,
2852 { email } ,
2953 )
3054
31- if ( existingUser . records . length > 0 ) {
32- throw new ApiError ( 400 , 'A user with this email already exists' )
55+ if ( emailCheckResult . records . length > 0 ) {
56+ const existingUser = emailCheckResult . records [ 0 ]
57+ const existingFirstName = existingUser . get ( 'firstName' )
58+ const existingLastName = existingUser . get ( 'lastName' )
59+
60+ throw new ApiError (
61+ 409 ,
62+ `There is already an account with this email "${ email } " for ${ existingFirstName } ${ existingLastName } ` ,
63+ )
3364 }
3465
35- // Hash password
66+ // 3. Check for duplicate WhatsApp number (if provided)
67+ if ( whatsappNumber ) {
68+ const whatsappCheckResult = await session . run (
69+ `MATCH (m:Member {whatsappNumber: $whatsappNumber})
70+ RETURN m.id as id, m.whatsappNumber as whatsappNumber, m.firstName as firstName, m.lastName as lastName
71+ LIMIT 1` ,
72+ { whatsappNumber } ,
73+ )
74+
75+ if ( whatsappCheckResult . records . length > 0 ) {
76+ const existingMember = whatsappCheckResult . records [ 0 ]
77+ const existingFirstName = existingMember . get ( 'firstName' )
78+ const existingLastName = existingMember . get ( 'lastName' )
79+
80+ throw new ApiError (
81+ 409 ,
82+ `There is already a member with this WhatsApp number "${ whatsappNumber } " for ${ existingFirstName } ${ existingLastName } ` ,
83+ )
84+ }
85+ }
86+
87+ // 4. Hash password
3688 const hashedPassword = await hashPassword ( password )
3789
38- // Create user
90+ // 5. Create user
3991 const result = await session . run (
4092 `CREATE (person:User)
4193 SET person.id = randomUUID(),
4294 person.email = $email,
4395 person.password = $password,
4496 person.firstName = $firstName,
4597 person.lastName = $lastName,
98+ person.whatsappNumber = $whatsappNumber,
99+ person.markedInactive = false,
46100 person.createdAt = datetime(),
47101 person.updatedAt = datetime()
48- RETURN person.id as id, person.email as email` ,
102+ RETURN person.id as id, person.email as email, person.firstName as firstName, person.lastName as lastName ` ,
49103 {
50104 email,
51105 password : hashedPassword ,
52- firstName : firstName || '' ,
53- lastName : lastName || '' ,
106+ firstName,
107+ lastName,
108+ whatsappNumber : whatsappNumber || null ,
54109 } ,
55110 )
56111
@@ -62,17 +117,29 @@ export const signup = asyncHandler(async (req: Request, res: Response) => {
62117 const userId = record . get ( 'id' )
63118 const userEmail = record . get ( 'email' )
64119
65- // Send welcome email (non-blocking)
66- sendWelcomeEmail ( userEmail , firstName || 'User' ) . catch ( ( error ) => {
120+ // 6. Send welcome email (non-blocking)
121+ sendWelcomeEmail ( userEmail , firstName ) . catch ( ( error ) => {
67122 console . error ( 'Failed to send welcome email:' , error )
68- // Don't fail the request if email fails
69123 } )
70124
125+ // 7. Log security event
126+ console . log (
127+ JSON . stringify ( {
128+ timestamp : new Date ( ) . toISOString ( ) ,
129+ event : 'user_signup' ,
130+ userId,
131+ email : userEmail ,
132+ clientIP : req . headers [ 'x-forwarded-for' ] || req . ip ,
133+ } ) ,
134+ )
135+
71136 res . status ( 201 ) . json ( {
72137 message : 'User created successfully' ,
73138 user : {
74139 id : userId ,
75140 email : userEmail ,
141+ firstName,
142+ lastName,
76143 } ,
77144 } )
78145 } catch ( error ) {
0 commit comments