2121using System . Text . RegularExpressions ;
2222using Google . Apis . Auth ;
2323using Microsoft . Extensions . Options ;
24- using WebApplication1 . Options ;
24+ using GroundShareAPI . Options ;
2525
2626namespace GroundShareAPI . Controllers
2727{
@@ -75,39 +75,42 @@ public async Task<IActionResult> Register([FromBody] RegisterRequest request)
7575 var streetName = Sanitize ( request . Street_Name ) ;
7676 var houseNumber = Sanitize ( request . House_Number ) ;
7777
78- // --- Email validation ---
78+ // Validation errors flow through ExceptionHandlingMiddleware so the
79+ // client sees one ApiResponse<object> shape for every failure.
80+ var fieldErrors = new Dictionary < string , string > ( ) ;
81+
7982 if ( string . IsNullOrEmpty ( email ) )
80- return BadRequest ( new { field = "email" , message = "כתובת מייל היא שדה חובה" } ) ;
81- if ( email . Length > 254 )
82- return BadRequest ( new { field = "email" , message = "כתובת מייל ארוכה מדי" } ) ;
83- if ( ! EmailRegex . IsMatch ( email ) )
84- return BadRequest ( new { field = "email" , message = "כתובת מייל לא תקינה" } ) ;
83+ fieldErrors [ "email" ] = "כתובת מייל היא שדה חובה" ;
84+ else if ( email . Length > 254 )
85+ fieldErrors [ "email" ] = "כתובת מייל ארוכה מדי" ;
86+ else if ( ! EmailRegex . IsMatch ( email ) )
87+ fieldErrors [ "email" ] = "כתובת מייל לא תקינה" ;
8588
86- // --- Password validation ---
8789 if ( string . IsNullOrEmpty ( password ) )
88- return BadRequest ( new { field = "password" , message = "סיסמה היא שדה חובה" } ) ;
89- if ( password . Length < 8 )
90- return BadRequest ( new { field = "password" , message = "הסיסמה חייבת להכיל לפחות 8 תווים" } ) ;
91- if ( password . Length > 128 )
92- return BadRequest ( new { field = "password" , message = "הסיסמה ארוכה מדי (מקסימום 128 תווים)" } ) ;
93- if ( ! password . Any ( char . IsUpper ) )
94- return BadRequest ( new { field = "password" , message = "הסיסמה חייבת לכלול לפחות אות גדולה באנגלית" } ) ;
95- if ( ! password . Any ( char . IsLower ) )
96- return BadRequest ( new { field = "password" , message = "הסיסמה חייבת לכלול לפחות אות קטנה באנגלית" } ) ;
97- if ( ! password . Any ( char . IsDigit ) )
98- return BadRequest ( new { field = "password" , message = "הסיסמה חייבת לכלול לפחות ספרה אחת" } ) ;
99-
100- // --- Full name validation ---
90+ fieldErrors [ "password" ] = "סיסמה היא שדה חובה" ;
91+ else if ( password . Length < 8 )
92+ fieldErrors [ "password" ] = "הסיסמה חייבת להכיל לפחות 8 תווים" ;
93+ else if ( password . Length > 128 )
94+ fieldErrors [ "password" ] = "הסיסמה ארוכה מדי (מקסימום 128 תווים)" ;
95+ else if ( ! password . Any ( char . IsUpper ) )
96+ fieldErrors [ "password" ] = "הסיסמה חייבת לכלול לפחות אות גדולה באנגלית" ;
97+ else if ( ! password . Any ( char . IsLower ) )
98+ fieldErrors [ "password" ] = "הסיסמה חייבת לכלול לפחות אות קטנה באנגלית" ;
99+ else if ( ! password . Any ( char . IsDigit ) )
100+ fieldErrors [ "password" ] = "הסיסמה חייבת לכלול לפחות ספרה אחת" ;
101+
101102 if ( string . IsNullOrEmpty ( fullName ) )
102- return BadRequest ( new { field = "fullName" , message = "שם מלא הוא שדה חובה" } ) ;
103- if ( fullName . Length < 2 )
104- return BadRequest ( new { field = "fullName" , message = "שם חייב להכיל לפחות 2 תווים" } ) ;
105- if ( fullName . Length > 50 )
106- return BadRequest ( new { field = "fullName" , message = "שם ארוך מדי (מקסימום 50 תווים)" } ) ;
103+ fieldErrors [ "fullName" ] = "שם מלא הוא שדה חובה" ;
104+ else if ( fullName . Length < 2 )
105+ fieldErrors [ "fullName" ] = "שם חייב להכיל לפחות 2 תווים" ;
106+ else if ( fullName . Length > 50 )
107+ fieldErrors [ "fullName" ] = "שם ארוך מדי (מקסימום 50 תווים)" ;
107108
108- // --- Phone validation (optional) ---
109109 if ( ! string . IsNullOrEmpty ( phone ) && ! PhoneRegex . IsMatch ( phone ) )
110- return BadRequest ( new { field = "phone" , message = "מספר טלפון לא תקין" } ) ;
110+ fieldErrors [ "phone" ] = "מספר טלפון לא תקין" ;
111+
112+ if ( fieldErrors . Count > 0 )
113+ throw new Exceptions . ValidationException ( "נתוני הרשמה אינם תקינים" , fieldErrors ) ;
111114
112115 try
113116 {
@@ -121,7 +124,7 @@ public async Task<IActionResult> Register([FromBody] RegisterRequest request)
121124 string . IsNullOrEmpty ( houseNumber ) ? null : houseNumber ) ;
122125
123126 if ( userRow == null )
124- return StatusCode ( 500 , new { message = "שגיאה בהרשמה" } ) ;
127+ throw new InvalidOperationException ( "User row was null after successful register." ) ;
125128
126129 int userId = Convert . ToInt32 ( userRow [ "User_ID" ] ) ;
127130 string accessToken = GenerateAccessToken ( userId , email ) ;
@@ -151,7 +154,7 @@ public async Task<IActionResult> Register([FromBody] RegisterRequest request)
151154 public async Task < IActionResult > Login ( [ FromBody ] LoginRequest request )
152155 {
153156 if ( string . IsNullOrWhiteSpace ( request . Email ) || string . IsNullOrWhiteSpace ( request . Password ) )
154- return BadRequest ( new { message = "Email and password are required." } ) ;
157+ throw new Exceptions . ValidationException ( "Email and password are required." ) ;
155158
156159 var userRow = await _usersDal . GetUserByEmailRawAsync ( request . Email ) ;
157160
@@ -174,7 +177,7 @@ public async Task<IActionResult> Login([FromBody] LoginRequest request)
174177 {
175178 await _audit . LogAsync ( null , "LOGIN_FAILURE" , ClientIp , ClientUserAgent ,
176179 JsonSerializer . Serialize ( new { email = request . Email } ) ) ;
177- return Unauthorized ( new { message = "אימות נכשל" } ) ;
180+ throw new UnauthorizedException ( "אימות נכשל" ) ;
178181 }
179182
180183 int userId = Convert . ToInt32 ( userRow [ "User_ID" ] ) ;
@@ -202,7 +205,7 @@ await _audit.LogAsync(null, "LOGIN_FAILURE", ClientIp, ClientUserAgent,
202205 public async Task < IActionResult > GoogleSignIn ( [ FromBody ] GoogleSignInRequest request )
203206 {
204207 if ( string . IsNullOrWhiteSpace ( request ? . IdToken ) )
205- return BadRequest ( new { message = "Missing Google credential." } ) ;
208+ throw new Exceptions . ValidationException ( "Missing Google credential." ) ;
206209
207210 // -----------------------------------------------------------------
208211 // Verify the ID token using Google's official library.
@@ -227,65 +230,58 @@ public async Task<IActionResult> GoogleSignIn([FromBody] GoogleSignInRequest req
227230 } ) ;
228231
229232 if ( ! payload . EmailVerified )
230- return Unauthorized ( new { message = "Google email is not verified." } ) ;
233+ throw new UnauthorizedException ( "Google email is not verified." ) ;
231234
232235 email = payload . Email ?? "" ;
233236 fullName = payload . Name ?? payload . GivenName ?? email . Split ( '@' ) [ 0 ] ;
234237
235238 if ( string . IsNullOrWhiteSpace ( email ) )
236- return Unauthorized ( new { message = "Google token missing email." } ) ;
239+ throw new UnauthorizedException ( "Google token missing email." ) ;
237240 }
238241 catch ( InvalidJwtException )
239242 {
240- return Unauthorized ( new { message = "Invalid Google token." } ) ;
243+ throw new UnauthorizedException ( "Invalid Google token." ) ;
241244 }
242245
243246 email = Sanitize ( email ) . ToLowerInvariant ( ) ;
244247 fullName = Sanitize ( fullName ) ;
245248 if ( fullName . Length > 50 ) fullName = fullName . Substring ( 0 , 50 ) ;
246249 if ( string . IsNullOrWhiteSpace ( fullName ) ) fullName = email . Split ( '@' ) [ 0 ] ;
247250
248- try
249- {
250- var userRow = await _socialAuthDal . UpsertGoogleUserAsync ( email , fullName ) ;
251- if ( userRow == null )
252- return StatusCode ( 500 , new { message = "Failed to create or load user." } ) ;
251+ var userRow = await _socialAuthDal . UpsertGoogleUserAsync ( email , fullName ) ;
252+ if ( userRow == null )
253+ throw new InvalidOperationException ( "Failed to create or load Google user." ) ;
253254
254- int userId = Convert . ToInt32 ( userRow [ "User_ID" ] ) ;
255- string accessToken = GenerateAccessToken ( userId , email ) ;
256- string refreshToken = GenerateRefreshToken ( ) ;
255+ int userId = Convert . ToInt32 ( userRow [ "User_ID" ] ) ;
256+ string accessToken = GenerateAccessToken ( userId , email ) ;
257+ string refreshToken = GenerateRefreshToken ( ) ;
257258
258- await _refreshTokenDal . SaveTokenAsync ( userId , refreshToken , DateTime . UtcNow . AddDays ( 7 ) ) ;
259- await _audit . LogAsync ( userId , "GOOGLE_SIGNIN" , ClientIp , ClientUserAgent ) ;
259+ await _refreshTokenDal . SaveTokenAsync ( userId , refreshToken , DateTime . UtcNow . AddDays ( 7 ) ) ;
260+ await _audit . LogAsync ( userId , "GOOGLE_SIGNIN" , ClientIp , ClientUserAgent ) ;
260261
261- return Ok ( new
262- {
263- accessToken ,
264- refreshToken ,
265- user = userRow
266- } ) ;
267- }
268- catch ( Exception )
262+ return Ok ( new
269263 {
270- return StatusCode ( 500 , new { message = "An error occurred during Google sign-in." } ) ;
271- }
264+ accessToken ,
265+ refreshToken ,
266+ user = userRow
267+ } ) ;
272268 }
273269
274270 [ HttpPost ( "refresh" ) ]
275271 [ EnableRateLimiting ( "auth-refresh" ) ]
276272 public async Task < IActionResult > Refresh ( [ FromBody ] RefreshRequest request )
277273 {
278274 if ( string . IsNullOrWhiteSpace ( request . RefreshToken ) )
279- return BadRequest ( new { message = "Refresh token is required." } ) ;
275+ throw new Exceptions . ValidationException ( "Refresh token is required." ) ;
280276
281277 var tokenRow = await _refreshTokenDal . GetTokenAsync ( request . RefreshToken ) ;
282278
283279 if ( tokenRow == null )
284- return Unauthorized ( new { message = "Invalid refresh token." } ) ;
280+ throw new UnauthorizedException ( "Invalid refresh token." ) ;
285281
286282 var expiresAt = Convert . ToDateTime ( tokenRow [ "Expires_At" ] ) ;
287283 if ( expiresAt < DateTime . UtcNow )
288- return Unauthorized ( new { message = "Refresh token expired." } ) ;
284+ throw new UnauthorizedException ( "Refresh token expired." ) ;
289285
290286 // Revoke old token
291287 await _refreshTokenDal . RevokeTokenAsync ( request . RefreshToken ) ;
@@ -295,7 +291,7 @@ public async Task<IActionResult> Refresh([FromBody] RefreshRequest request)
295291 // Get user data
296292 var userRow = await _usersDal . GetUserByIdAsync ( userId ) ;
297293 if ( userRow == null )
298- return Unauthorized ( new { message = "User not found." } ) ;
294+ throw new UnauthorizedException ( "User not found." ) ;
299295
300296 string email = userRow [ "Email" ] ? . ToString ( ) ?? "" ;
301297 string newAccessToken = GenerateAccessToken ( userId , email ) ;
0 commit comments