Skip to content

Commit eb8fab3

Browse files
committed
feat: add Next.js compatible API endpoints and refactor auth
- Add Next.js compatible Message API endpoints: - GET /messages/{conversationId} for message history - POST /messages/send with conversationId in body - PUT /messages/{conversationId}/read for marking read - DELETE /messages/{conversationId} for conversation deletion - Add SendMessageWithConversationRequest DTO - Add deleteConversation method in MessageService - Refactor TeamController, FolderController, CalendarController to use @AuthenticationPrincipal UserPrincipal instead of Authentication - Remove deprecated getUserId helper methods
1 parent ee8a02c commit eb8fab3

7 files changed

Lines changed: 153 additions & 147 deletions

File tree

fly.toml

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
1-
# fly.toml app configuration file generated for halolight-api-java on 2025-12-09
2-
#
3-
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4-
#
5-
6-
app = 'halolight-api-java'
7-
primary_region = 'hkg'
1+
app = "halolight-api-java"
2+
primary_region = "sjc"
83

94
[build]
10-
dockerfile = "Dockerfile"
115

126
[env]
13-
PORT = "8000"
7+
PORT = "8000"
8+
SPRING_PROFILES_ACTIVE = "prod"
149

1510
[http_service]
16-
internal_port = 8000
17-
force_https = true
18-
auto_stop_machines = 'suspend'
19-
auto_start_machines = true
20-
min_machines_running = 1
21-
processes = ['app']
11+
auto_start_machines = true
12+
auto_stop_machines = "suspend"
13+
force_https = true
14+
internal_port = 8000
15+
min_machines_running = 1
16+
processes = ["app"]
2217

23-
# Health check configuration
24-
[[http_service.http_checks]]
25-
grace_period = "10s"
26-
interval = "30s"
27-
method = "GET"
28-
timeout = "5s"
29-
path = "/actuator/health"
18+
[[http_service.checks]]
19+
grace_period = "60s"
20+
interval = "30s"
21+
method = "GET"
22+
path = "/actuator/health"
23+
protocol = "http"
24+
timeout = "10s"
3025

3126
[[vm]]
32-
memory = '1gb'
33-
cpu_kind = 'shared'
34-
cpus = 1
27+
cpu_kind = "shared"
28+
cpus = 1
29+
memory = "1gb"

src/main/java/com/halolight/controller/CalendarController.java

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.halolight.controller;
22

33
import com.halolight.dto.ApiResponse;
4+
import com.halolight.security.UserPrincipal;
45
import com.halolight.service.CalendarService;
56
import com.halolight.web.dto.calendar.CreateEventRequest;
67
import com.halolight.web.dto.calendar.EventResponse;
@@ -19,7 +20,7 @@
1920
import org.springframework.format.annotation.DateTimeFormat;
2021
import org.springframework.http.HttpStatus;
2122
import org.springframework.http.ResponseEntity;
22-
import org.springframework.security.core.Authentication;
23+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
2324
import org.springframework.web.bind.annotation.*;
2425

2526
import java.time.Instant;
@@ -38,14 +39,13 @@ public class CalendarController {
3839
@Operation(summary = "Get all events", description = "Retrieve all calendar events for the current user with optional date range filter")
3940
@GetMapping
4041
public ResponseEntity<ApiResponse<List<EventResponse>>> getAllEvents(
41-
Authentication authentication,
42+
@AuthenticationPrincipal UserPrincipal user,
4243
@Parameter(description = "Start date (ISO-8601 format)")
4344
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant start,
4445
@Parameter(description = "End date (ISO-8601 format)")
4546
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Instant end
4647
) {
47-
String userId = getUserId(authentication);
48-
List<EventResponse> events = calendarService.getAllEvents(userId, start, end);
48+
List<EventResponse> events = calendarService.getAllEvents(user.getId(), start, end);
4949
return ResponseEntity.ok(ApiResponse.success(events));
5050
}
5151

@@ -89,11 +89,10 @@ public ResponseEntity<ApiResponse<EventResponse>> getEventById(@PathVariable Str
8989
@Operation(summary = "Create event", description = "Create a new calendar event")
9090
@PostMapping
9191
public ResponseEntity<ApiResponse<EventResponse>> createEvent(
92-
Authentication authentication,
92+
@AuthenticationPrincipal UserPrincipal user,
9393
@Valid @RequestBody CreateEventRequest request
9494
) {
95-
String userId = getUserId(authentication);
96-
EventResponse event = calendarService.createEvent(userId, request);
95+
EventResponse event = calendarService.createEvent(user.getId(), request);
9796
return ResponseEntity.status(HttpStatus.CREATED)
9897
.body(ApiResponse.success("Event created successfully", event));
9998
}
@@ -102,49 +101,45 @@ public ResponseEntity<ApiResponse<EventResponse>> createEvent(
102101
@PutMapping("/{id}")
103102
public ResponseEntity<ApiResponse<EventResponse>> updateEvent(
104103
@PathVariable String id,
105-
Authentication authentication,
104+
@AuthenticationPrincipal UserPrincipal user,
106105
@Valid @RequestBody UpdateEventRequest request
107106
) {
108-
String userId = getUserId(authentication);
109-
EventResponse event = calendarService.updateEvent(id, userId, request);
107+
EventResponse event = calendarService.updateEvent(id, user.getId(), request);
110108
return ResponseEntity.ok(ApiResponse.success("Event updated successfully", event));
111109
}
112110

113111
@Operation(summary = "Delete event", description = "Delete a calendar event")
114112
@DeleteMapping("/{id}")
115113
public ResponseEntity<ApiResponse<Void>> deleteEvent(
116114
@PathVariable String id,
117-
Authentication authentication
115+
@AuthenticationPrincipal UserPrincipal user
118116
) {
119-
String userId = getUserId(authentication);
120-
calendarService.deleteEvent(id, userId);
117+
calendarService.deleteEvent(id, user.getId());
121118
return ResponseEntity.ok(ApiResponse.success("Event deleted successfully", null));
122119
}
123120

124121
@Operation(summary = "Batch delete events", description = "Delete multiple calendar events")
125122
@PostMapping("/batch-delete")
126123
public ResponseEntity<ApiResponse<Void>> batchDeleteEvents(
127-
Authentication authentication,
124+
@AuthenticationPrincipal UserPrincipal user,
128125
@RequestBody Map<String, List<String>> body
129126
) {
130-
String userId = getUserId(authentication);
131127
List<String> ids = body.get("ids");
132128
if (ids == null || ids.isEmpty()) {
133129
return ResponseEntity.badRequest()
134130
.body(ApiResponse.error("Event IDs are required"));
135131
}
136-
calendarService.batchDeleteEvents(ids, userId);
132+
calendarService.batchDeleteEvents(ids, user.getId());
137133
return ResponseEntity.ok(ApiResponse.success("Events deleted successfully", null));
138134
}
139135

140136
@Operation(summary = "Reschedule event", description = "Reschedule a calendar event to a new time")
141137
@PatchMapping("/{id}/reschedule")
142138
public ResponseEntity<ApiResponse<EventResponse>> rescheduleEvent(
143139
@PathVariable String id,
144-
Authentication authentication,
140+
@AuthenticationPrincipal UserPrincipal user,
145141
@RequestBody Map<String, Instant> body
146142
) {
147-
String userId = getUserId(authentication);
148143
Instant newStart = body.get("start");
149144
Instant newEnd = body.get("end");
150145

@@ -153,26 +148,25 @@ public ResponseEntity<ApiResponse<EventResponse>> rescheduleEvent(
153148
.body(ApiResponse.error("Both start and end times are required"));
154149
}
155150

156-
EventResponse event = calendarService.rescheduleEvent(id, userId, newStart, newEnd);
151+
EventResponse event = calendarService.rescheduleEvent(id, user.getId(), newStart, newEnd);
157152
return ResponseEntity.ok(ApiResponse.success("Event rescheduled successfully", event));
158153
}
159154

160155
@Operation(summary = "Add attendees", description = "Add attendees to a calendar event")
161156
@PostMapping("/{id}/attendees")
162157
public ResponseEntity<ApiResponse<EventResponse>> addAttendees(
163158
@PathVariable String id,
164-
Authentication authentication,
159+
@AuthenticationPrincipal UserPrincipal user,
165160
@RequestBody Map<String, List<String>> body
166161
) {
167-
String userId = getUserId(authentication);
168162
List<String> attendeeIds = body.get("attendeeIds");
169163

170164
if (attendeeIds == null || attendeeIds.isEmpty()) {
171165
return ResponseEntity.badRequest()
172166
.body(ApiResponse.error("Attendee IDs are required"));
173167
}
174168

175-
EventResponse event = calendarService.addAttendees(id, userId, attendeeIds);
169+
EventResponse event = calendarService.addAttendees(id, user.getId(), attendeeIds);
176170
return ResponseEntity.ok(ApiResponse.success("Attendees added successfully", event));
177171
}
178172

@@ -181,10 +175,9 @@ public ResponseEntity<ApiResponse<EventResponse>> addAttendees(
181175
public ResponseEntity<ApiResponse<EventResponse>> removeAttendee(
182176
@PathVariable String id,
183177
@PathVariable String attendeeId,
184-
Authentication authentication
178+
@AuthenticationPrincipal UserPrincipal user
185179
) {
186-
String userId = getUserId(authentication);
187-
EventResponse event = calendarService.removeAttendee(id, userId, attendeeId);
180+
EventResponse event = calendarService.removeAttendee(id, user.getId(), attendeeId);
188181
return ResponseEntity.ok(ApiResponse.success("Attendee removed successfully", event));
189182
}
190183

@@ -198,25 +191,4 @@ public ResponseEntity<ApiResponse<EventResponse>> updateAttendeeStatus(
198191
EventResponse event = calendarService.updateAttendeeStatus(id, userId, request.getStatus());
199192
return ResponseEntity.ok(ApiResponse.success("Attendee status updated successfully", event));
200193
}
201-
202-
/**
203-
* Extract user ID from authentication.
204-
* This assumes the authentication principal contains user information.
205-
* Adjust based on your actual security configuration.
206-
*/
207-
private String getUserId(Authentication authentication) {
208-
// For now, we'll use the username as ID
209-
// TODO: Update this based on your actual UserPrincipal implementation
210-
// If UserPrincipal has been updated to use String id, use:
211-
// return ((UserPrincipal) authentication.getPrincipal()).getId();
212-
213-
// Temporary solution - assumes username is the user ID or can be resolved
214-
Object principal = authentication.getPrincipal();
215-
if (principal instanceof String) {
216-
return (String) principal;
217-
}
218-
// If you have a custom UserPrincipal, cast and get ID
219-
// For now, using authentication name as fallback
220-
return authentication.getName();
221-
}
222194
}
Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.halolight.controller;
22

33
import com.halolight.dto.ApiResponse;
4+
import com.halolight.security.UserPrincipal;
45
import com.halolight.service.FolderService;
56
import com.halolight.web.dto.folder.CreateFolderRequest;
67
import com.halolight.web.dto.folder.FolderResponse;
@@ -13,7 +14,7 @@
1314
import lombok.RequiredArgsConstructor;
1415
import org.springframework.http.HttpStatus;
1516
import org.springframework.http.ResponseEntity;
16-
import org.springframework.security.core.Authentication;
17+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1718
import org.springframework.web.bind.annotation.*;
1819

1920
import java.util.List;
@@ -34,97 +35,79 @@ public class FolderController {
3435
@Operation(summary = "Create folder", description = "Create a new folder")
3536
@PostMapping
3637
public ResponseEntity<ApiResponse<FolderResponse>> createFolder(
37-
Authentication authentication,
38+
@AuthenticationPrincipal UserPrincipal user,
3839
@Valid @RequestBody CreateFolderRequest request
3940
) {
40-
String userId = getUserId(authentication);
41-
FolderResponse folder = folderService.createFolder(userId, request);
41+
FolderResponse folder = folderService.createFolder(user.getId(), request);
4242
return ResponseEntity.status(HttpStatus.CREATED)
4343
.body(ApiResponse.success("Folder created successfully", folder));
4444
}
4545

4646
@Operation(summary = "Get folders", description = "Get list of folders with optional parent filter")
4747
@GetMapping
4848
public ResponseEntity<ApiResponse<List<FolderResponse>>> getFolders(
49-
Authentication authentication,
49+
@AuthenticationPrincipal UserPrincipal user,
5050
@Parameter(description = "Parent folder ID") @RequestParam(required = false) String parentId
5151
) {
52-
String userId = getUserId(authentication);
53-
List<FolderResponse> folders = folderService.getFolders(userId, parentId);
52+
List<FolderResponse> folders = folderService.getFolders(user.getId(), parentId);
5453
return ResponseEntity.ok(ApiResponse.success(folders));
5554
}
5655

5756
@Operation(summary = "Get folder by ID", description = "Get folder details by ID")
5857
@GetMapping("/{id}")
5958
public ResponseEntity<ApiResponse<FolderResponse>> getFolderById(
6059
@PathVariable String id,
61-
Authentication authentication
60+
@AuthenticationPrincipal UserPrincipal user
6261
) {
63-
String userId = getUserId(authentication);
64-
FolderResponse folder = folderService.getFolderById(id, userId);
62+
FolderResponse folder = folderService.getFolderById(id, user.getId());
6563
return ResponseEntity.ok(ApiResponse.success(folder));
6664
}
6765

6866
@Operation(summary = "Update folder", description = "Update folder name and/or parent")
6967
@PutMapping("/{id}")
7068
public ResponseEntity<ApiResponse<FolderResponse>> updateFolder(
7169
@PathVariable String id,
72-
Authentication authentication,
70+
@AuthenticationPrincipal UserPrincipal user,
7371
@Valid @RequestBody UpdateFolderRequest request
7472
) {
75-
String userId = getUserId(authentication);
76-
FolderResponse folder = folderService.updateFolder(id, userId, request);
73+
FolderResponse folder = folderService.updateFolder(id, user.getId(), request);
7774
return ResponseEntity.ok(ApiResponse.success("Folder updated successfully", folder));
7875
}
7976

8077
@Operation(summary = "Rename folder", description = "Rename a folder")
8178
@PatchMapping("/{id}")
8279
public ResponseEntity<ApiResponse<FolderResponse>> renameFolder(
8380
@PathVariable String id,
84-
Authentication authentication,
81+
@AuthenticationPrincipal UserPrincipal user,
8582
@RequestBody Map<String, String> body
8683
) {
87-
String userId = getUserId(authentication);
8884
String newName = body.get("name");
8985

9086
if (newName == null || newName.trim().isEmpty()) {
9187
return ResponseEntity.badRequest()
9288
.body(ApiResponse.error("Folder name cannot be blank"));
9389
}
9490

95-
FolderResponse folder = folderService.renameFolder(id, userId, newName);
91+
FolderResponse folder = folderService.renameFolder(id, user.getId(), newName);
9692
return ResponseEntity.ok(ApiResponse.success("Folder renamed successfully", folder));
9793
}
9894

9995
@Operation(summary = "Delete folder", description = "Delete a folder (must be empty)")
10096
@DeleteMapping("/{id}")
10197
public ResponseEntity<ApiResponse<Void>> deleteFolder(
10298
@PathVariable String id,
103-
Authentication authentication
99+
@AuthenticationPrincipal UserPrincipal user
104100
) {
105-
String userId = getUserId(authentication);
106-
folderService.deleteFolder(id, userId);
101+
folderService.deleteFolder(id, user.getId());
107102
return ResponseEntity.ok(ApiResponse.success("Folder deleted successfully", null));
108103
}
109104

110105
@Operation(summary = "Get folder tree", description = "Get hierarchical folder tree structure")
111106
@GetMapping("/tree")
112107
public ResponseEntity<ApiResponse<List<FolderService.FolderTreeNode>>> getFolderTree(
113-
Authentication authentication
108+
@AuthenticationPrincipal UserPrincipal user
114109
) {
115-
String userId = getUserId(authentication);
116-
List<FolderService.FolderTreeNode> tree = folderService.getFolderTree(userId);
110+
List<FolderService.FolderTreeNode> tree = folderService.getFolderTree(user.getId());
117111
return ResponseEntity.ok(ApiResponse.success(tree));
118112
}
119-
120-
/**
121-
* Extract user ID from authentication
122-
*/
123-
private String getUserId(Authentication authentication) {
124-
Object principal = authentication.getPrincipal();
125-
if (principal instanceof String) {
126-
return (String) principal;
127-
}
128-
return authentication.getName();
129-
}
130113
}

0 commit comments

Comments
 (0)