88using System . Security . Claims ;
99using System ;
1010using System . Collections . Generic ;
11+ using System . Linq ;
12+ using System . Text . Json ;
1113using FlowModels = Flowboard_Project_Management_System_Backend . Models . FlowboardModel ;
1214
1315[ ApiController ]
1618public class UsersController : ControllerBase
1719{
1820 private readonly MongoDbService _mongoDbService ;
21+ private const long MaxImageSizeBytes = 5 * 1024 * 1024 ; // 5MB max
22+ private static readonly string [ ] AllowedImageTypes = { "image/jpeg" , "image/png" , "image/gif" , "image/webp" } ;
1923
2024 public UsersController ( MongoDbService mongoDbService )
2125 {
@@ -36,18 +40,104 @@ public UsersController(MongoDbService mongoDbService)
3640 return string . IsNullOrWhiteSpace ( userId ) ? null : userId ;
3741 }
3842
43+ // Helper: Convert byte[] to base64 data URL
44+ private static string ? BytesToDataUrl ( byte [ ] ? imageBytes )
45+ {
46+ if ( imageBytes == null || imageBytes . Length == 0 ) return null ;
47+
48+ // Detect image type from magic bytes
49+ string mimeType = "image/png" ; // default
50+ if ( imageBytes . Length >= 3 && imageBytes [ 0 ] == 0xFF && imageBytes [ 1 ] == 0xD8 && imageBytes [ 2 ] == 0xFF )
51+ mimeType = "image/jpeg" ;
52+ else if ( imageBytes . Length >= 8 && imageBytes [ 0 ] == 0x89 && imageBytes [ 1 ] == 0x50 && imageBytes [ 2 ] == 0x4E && imageBytes [ 3 ] == 0x47 )
53+ mimeType = "image/png" ;
54+ else if ( imageBytes . Length >= 6 && imageBytes [ 0 ] == 0x47 && imageBytes [ 1 ] == 0x49 && imageBytes [ 2 ] == 0x46 )
55+ mimeType = "image/gif" ;
56+ else if ( imageBytes . Length >= 4 && imageBytes [ 0 ] == 0x52 && imageBytes [ 1 ] == 0x49 && imageBytes [ 2 ] == 0x46 && imageBytes [ 3 ] == 0x46 )
57+ mimeType = "image/webp" ;
58+
59+ return $ "data:{ mimeType } ;base64,{ Convert . ToBase64String ( imageBytes ) } ";
60+ }
61+
62+ // Helper: Convert base64 data URL to byte[]
63+ private static byte [ ] ? DataUrlToBytes ( string ? dataUrl )
64+ {
65+ if ( string . IsNullOrWhiteSpace ( dataUrl ) ) return null ;
66+
67+ try
68+ {
69+ // Handle data URL format: data:image/png;base64,iVBORw0KGgo...
70+ if ( dataUrl . StartsWith ( "data:" ) )
71+ {
72+ var commaIndex = dataUrl . IndexOf ( ',' ) ;
73+ if ( commaIndex > 0 )
74+ {
75+ dataUrl = dataUrl . Substring ( commaIndex + 1 ) ;
76+ }
77+ }
78+
79+ return Convert . FromBase64String ( dataUrl ) ;
80+ }
81+ catch
82+ {
83+ return null ;
84+ }
85+ }
86+
87+ // Helper: Convert User to response DTO with base64 image
88+ private object UserToResponse ( FlowModels . User user )
89+ {
90+ return new
91+ {
92+ id = user . Id ,
93+ userName = user . UserName ,
94+ firstName = user . FirstName ,
95+ lastName = user . LastName ,
96+ middleName = user . MiddleName ,
97+ contactNumber = user . ContactNumber ,
98+ birthDate = user . BirthDate ,
99+ email = user . Email ,
100+ userIMG = BytesToDataUrl ( user . UserIMG ) ,
101+ createdAt = user . CreatedAt
102+ } ;
103+ }
104+
105+ // Helper: Extract string value from object (handles JsonElement)
106+ private static string ? GetStringValue ( object ? value )
107+ {
108+ if ( value == null ) return null ;
109+
110+ if ( value is JsonElement jsonElement )
111+ {
112+ return jsonElement . ValueKind == JsonValueKind . Null ? null : jsonElement . GetString ( ) ?? jsonElement . ToString ( ) ;
113+ }
114+
115+ return value . ToString ( ) ;
116+ }
117+
118+ // Helper: Check if value is null (handles JsonElement)
119+ private static bool IsNullValue ( object ? value )
120+ {
121+ if ( value == null ) return true ;
122+
123+ if ( value is JsonElement jsonElement )
124+ {
125+ return jsonElement . ValueKind == JsonValueKind . Null ;
126+ }
127+
128+ return false ;
129+ }
130+
39131 // Returns all users (passwords stripped) for assignment dropdowns
40132 [ HttpGet ]
41133 public IActionResult GetAll ( )
42134 {
43135 var db = _mongoDbService . GetDatabase ( ) ;
44136 var usersCollection = db . GetCollection < FlowModels . User > ( "user" ) ;
45137 var users = usersCollection . Find ( _ => true ) . ToList ( ) ;
46- foreach ( var u in users )
47- {
48- u . Password = string . Empty ;
49- }
50- return Ok ( users ) ;
138+
139+ var response = users . Select ( u => UserToResponse ( u ) ) . ToList ( ) ;
140+ return Ok ( response ) ;
51141 }
52142
53143 // Get a user by ID (still protected)
@@ -61,8 +151,7 @@ public IActionResult GetById(string id)
61151 var user = usersCollection . Find ( u => u . Id == id ) . FirstOrDefault ( ) ;
62152 if ( user == null ) return NotFound ( new { message = "User not found." } ) ;
63153
64- user . Password = string . Empty ;
65- return Ok ( user ) ;
154+ return Ok ( UserToResponse ( user ) ) ;
66155 }
67156
68157 // PATCH /api/users/{id} - Partial update (only provided fields are updated)
@@ -89,7 +178,7 @@ public IActionResult Patch(string id, [FromBody] Dictionary<string, object> upda
89178 // email conflict check (done early if present)
90179 if ( updates . TryGetValue ( "email" , out var emailObj ) && emailObj != null )
91180 {
92- var emailStr = emailObj . ToString ( ) ? . Trim ( ) ?? string . Empty ;
181+ var emailStr = GetStringValue ( emailObj ) ? . Trim ( ) ?? string . Empty ;
93182 if ( ! string . IsNullOrEmpty ( emailStr ) )
94183 {
95184 var existing = usersCollection . Find ( u => u . Email . ToLower ( ) == emailStr . ToLower ( ) ) . FirstOrDefault ( ) ;
@@ -103,51 +192,62 @@ public IActionResult Patch(string id, [FromBody] Dictionary<string, object> upda
103192 {
104193 var key = kv . Key . ToLowerInvariant ( ) ;
105194 var value = kv . Value ;
195+ var stringValue = GetStringValue ( value ) ;
196+
106197 switch ( key )
107198 {
108199 case "username" :
109- case "userName" :
110200 case "user_name" :
111- updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . UserName , value ? . ToString ( ) ) ) ;
201+ if ( ! string . IsNullOrWhiteSpace ( stringValue ) )
202+ updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . UserName , stringValue ) ) ;
112203 break ;
113204 case "firstname" :
114- case "firstName" :
115- updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . FirstName , value ? . ToString ( ) ) ) ;
205+ if ( ! string . IsNullOrWhiteSpace ( stringValue ) )
206+ updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . FirstName , stringValue ) ) ;
116207 break ;
117208 case "lastname" :
118- case "lastName" :
119- updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . LastName , value ? . ToString ( ) ) ) ;
209+ if ( ! string . IsNullOrWhiteSpace ( stringValue ) )
210+ updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . LastName , stringValue ) ) ;
120211 break ;
121212 case "middlename" :
122- case "middleName" :
123- updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . MiddleName , value ? . ToString ( ) ) ) ;
213+ updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . MiddleName , stringValue ?? string . Empty ) ) ;
124214 break ;
125215 case "contactnumber" :
126216 case "contact" :
127217 case "contact_number" :
128- updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . ContactNumber , value ? . ToString ( ) ) ) ;
218+ if ( ! string . IsNullOrWhiteSpace ( stringValue ) )
219+ updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . ContactNumber , stringValue ) ) ;
129220 break ;
130221 case "birthdate" :
131222 case "birth_date" :
132- if ( DateTime . TryParse ( value ? . ToString ( ) , out var birthDate ) )
223+ if ( DateTime . TryParse ( stringValue , out var birthDate ) )
133224 updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . BirthDate , birthDate ) ) ;
134225 break ;
135226 case "userimg" :
136227 case "user_img" :
137228 case "user_img_base64" :
138- if ( value is string base64 && ! string . IsNullOrWhiteSpace ( base64 ) )
229+ if ( IsNullValue ( value ) )
230+ {
231+ // Allow clearing the image
232+ updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . UserIMG , ( byte [ ] ? ) null ) ) ;
233+ }
234+ else if ( ! string . IsNullOrWhiteSpace ( stringValue ) )
139235 {
140- try {
141- var bytes = Convert . FromBase64String ( base64 ) ;
236+ var bytes = DataUrlToBytes ( stringValue ) ;
237+ if ( bytes != null )
238+ {
239+ if ( bytes . Length > MaxImageSizeBytes )
240+ {
241+ return BadRequest ( new { message = "Image size exceeds 5MB limit." } ) ;
242+ }
142243 updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . UserIMG , bytes ) ) ;
143244 }
144- catch { /* invalid base64; ignore or respond with BadRequest? For now ignore */ }
145245 }
146246 break ;
147247 case "password" :
148- if ( ! string . IsNullOrWhiteSpace ( value ? . ToString ( ) ) )
248+ if ( ! string . IsNullOrWhiteSpace ( stringValue ) )
149249 {
150- var hashed = BCrypt . Net . BCrypt . HashPassword ( value ? . ToString ( ) ?? string . Empty ) ;
250+ var hashed = BCrypt . Net . BCrypt . HashPassword ( stringValue ) ;
151251 updateDefs . Add ( Builders < FlowModels . User > . Update . Set ( u => u . Password , hashed ) ) ;
152252 }
153253 break ;
@@ -174,8 +274,9 @@ public IActionResult Patch(string id, [FromBody] Dictionary<string, object> upda
174274 return NotFound ( new { message = "User not found." } ) ;
175275
176276 var updatedUser = usersCollection . Find ( u => u . Id == id ) . FirstOrDefault ( ) ;
177- if ( updatedUser != null ) updatedUser . Password = string . Empty ;
178- return Ok ( updatedUser ) ;
277+ if ( updatedUser == null ) return NotFound ( new { message = "User not found after update." } ) ;
278+
279+ return Ok ( UserToResponse ( updatedUser ) ) ;
179280 }
180281 catch ( Exception ex )
181282 {
0 commit comments