11import { NextResponse } from "next/server"
22import { User } from "@tasktrove/types/core"
33import { InitialSetupResponse } from "@tasktrove/types/api-responses"
4- import { InitialSetupRequestSchema } from "@tasktrove/types/api-requests"
54import { ErrorResponse } from "@tasktrove/types/api-responses"
6- import { ApiErrorCode } from "@tasktrove/types/api-errors"
7- import { validateRequestBody , createErrorResponse } from "@/lib/utils/validation"
85import { safeReadDataFile , safeWriteDataFile } from "@/lib/utils/safe-file-operations"
96import {
107 withApiLogging ,
11- logBusinessEvent ,
128 withFileOperationLogging ,
139 withPerformanceLogging ,
1410 type EnhancedRequest ,
1511} from "@/lib/middleware/api-logger"
1612import { withMutexProtection } from "@/lib/utils/api-mutex"
17- import { saltAndHashPassword } from "@tasktrove/utils"
1813import { initializeDataFileIfNeeded } from "@/lib/utils/data-initialization"
14+ import { runInitialSetup } from "@/lib/utils/initial-setup"
1915
2016/**
2117 * POST /api/(auth)/initial-setup
@@ -26,113 +22,44 @@ import { initializeDataFileIfNeeded } from "@/lib/utils/data-initialization"
2622async function initialSetup (
2723 request : EnhancedRequest ,
2824) : Promise < NextResponse < InitialSetupResponse | ErrorResponse > > {
29- // Validate request body
30- const validation = await validateRequestBody ( request , InitialSetupRequestSchema )
31- if ( ! validation . success ) {
32- return validation . error
33- }
34-
35- const { password, username } = validation . data
36-
37- // Read current data file to check existing password
38- let fileData = await withFileOperationLogging (
39- ( ) => safeReadDataFile ( ) ,
40- "read-data-file" ,
41- request . context ,
42- )
43-
44- // If data file read failed, try to initialize it and read again
45- if ( ! fileData ) {
46- const initSuccess = await initializeDataFileIfNeeded ( )
47- if ( ! initSuccess ) {
48- return createErrorResponse (
49- "Failed to initialize data file" ,
50- "Data file initialization failed" ,
51- 500 ,
52- )
53- }
54-
55- // Retry reading the data file after initialization
56- fileData = await withFileOperationLogging (
57- ( ) => safeReadDataFile ( ) ,
58- "read-data-file-retry" ,
59- request . context ,
60- )
61-
62- if ( ! fileData ) {
63- return createErrorResponse (
64- "Failed to read data file after initialization" ,
65- "File reading failed after initialization" ,
66- 500 ,
67- )
68- }
69- }
70-
71- // Check if password is already set - only allow initial setup if password is empty
72- if ( fileData . user . password !== "" ) {
73- return createErrorResponse (
74- "Password already set" ,
75- "Initial setup is only allowed when no password is currently set" ,
76- 409 , // Conflict
77- )
78- }
79-
80- // Hash the password
81- let hashedPassword : string
82- try {
83- hashedPassword = saltAndHashPassword ( password )
84- } catch {
85- return createErrorResponse (
86- "Failed to hash password" ,
87- "Password hashing failed" ,
88- 500 ,
89- ApiErrorCode . INTERNAL_SERVER_ERROR ,
90- )
91- }
92-
93- // Update user with the new password (keeping existing username and avatar unless username is provided)
94- const updatedUser : User = {
95- ...fileData . user ,
96- ...( username && { username } ) ,
97- password : hashedPassword ,
98- }
99-
100- // Update the data file with new user data
101- const updatedFileData = {
102- ...fileData ,
103- user : updatedUser ,
104- }
105-
106- // Write updated data to file
107- const writeSuccess = await withPerformanceLogging (
108- ( ) => safeWriteDataFile ( { data : updatedFileData } ) ,
109- "write-data-file" ,
110- request . context ,
111- 500 , // 500ms threshold for slow file writes
112- )
113-
114- if ( ! writeSuccess ) {
115- return createErrorResponse (
116- "Failed to save data" ,
117- "File writing failed" ,
118- 500 ,
119- ApiErrorCode . DATA_FILE_WRITE_ERROR ,
120- )
121- }
122-
123- logBusinessEvent (
124- "initial_setup_completed" ,
125- {
126- username : updatedUser . username ,
25+ type SafeDataFile = NonNullable < Awaited < ReturnType < typeof safeReadDataFile > > >
26+
27+ return runInitialSetup < SafeDataFile > ( {
28+ request,
29+ readData : ( ) =>
30+ withFileOperationLogging (
31+ async ( ) => ( await safeReadDataFile ( ) ) ?? null ,
32+ "read-data-file" ,
33+ request . context ,
34+ ) ,
35+ initializeIfNeeded : ( ) => initializeDataFileIfNeeded ( ) ,
36+ isPasswordSet : ( fileData ) => fileData . user . password !== "" ,
37+ buildUpdatedData : ( fileData , { passwordHash, username } ) => {
38+ const updatedUser : User = {
39+ ...fileData . user ,
40+ ...( username && { username } ) ,
41+ password : passwordHash ,
42+ }
43+
44+ return {
45+ updatedData : {
46+ ...fileData ,
47+ user : updatedUser ,
48+ } ,
49+ logEvent : {
50+ username : updatedUser . username ,
51+ } ,
52+ }
12753 } ,
128- request . context ,
129- )
130-
131- const response : InitialSetupResponse = {
132- success : true ,
133- }
134-
135- return NextResponse . json ( response )
54+ writeData : ( updatedFileData ) =>
55+ withPerformanceLogging (
56+ ( ) => safeWriteDataFile ( { data : updatedFileData } ) ,
57+ "write-data-file" ,
58+ request . context ,
59+ 500 ,
60+ ) ,
61+ passwordAlreadySetMessage : "Initial setup is only allowed when no password is currently set" ,
62+ } )
13663}
13764
13865export const POST = withMutexProtection (
0 commit comments