1- "use server"
1+ "use server" ;
2+
3+ import { z } from "zod" ;
4+ import { db } from "@/prisma/db" ;
5+ import { callTraccarAdminApi , createTraccarUser } from "@/config/callTraccarAdminApi" ;
6+ import { sendActivationEmail } from "@/lib/emailservice" ;
7+ import { randomUUID } from "crypto" ;
8+ import { ClientCreationProps } from "@/types/types" ;
9+ import { Prisma } from "@prisma/client" ;
10+
11+
12+
13+ const DeleteUserIdSchema = z . number ( ) . int ( ) . min ( 1 , "User ID must be a positive integer." ) ;
14+
15+ export async function deleteUser ( userId : number ) {
16+ // 1. Input Validation
17+ const validatedId = DeleteUserIdSchema . safeParse ( userId ) ;
18+
19+ if ( ! validatedId . success ) {
20+ return {
21+ error : validatedId . error . message ,
22+ status : 400 ,
23+ } ;
24+ }
25+
26+ try {
27+ // 2. Find the user in the local database
28+ const userToDelete = await db . user . findUnique ( {
29+ where : { id : validatedId . data } ,
30+ } ) ;
31+
32+ if ( ! userToDelete ) {
33+ return {
34+ error : "User not found in local database." ,
35+ status : 404 ,
36+ } ;
37+ }
38+
39+ // 3. Delete user from Traccar (if a traccarId exists)
40+ if ( userToDelete . traccarId ) {
41+ await callTraccarAdminApi ( `users/${ userToDelete . traccarId } ` , 'DELETE' ) ;
42+ console . log ( `User ${ userToDelete . email } (Traccar ID: ${ userToDelete . traccarId } ) deleted from Traccar.` ) ;
43+ } else {
44+ console . log ( `User ${ userToDelete . email } has no Traccar ID. Skipping Traccar deletion.` ) ;
45+ }
46+
47+ // 4. Delete user from your local Prisma database
48+ await db . user . delete ( {
49+ where : { id : userToDelete . id } ,
50+ } ) ;
51+
52+ console . log ( `User ${ userToDelete . email } deleted from local database.` ) ;
53+
54+ return {
55+ success : true ,
56+ message : "User deleted successfully from both systems." ,
57+ status : 200 ,
58+ } ;
59+
60+ } catch ( error ) {
61+ console . error ( "Error deleting user:" , error ) ;
62+ return {
63+ error : "An unexpected error occurred while deleting the user. Please try again." ,
64+ status : 500 ,
65+ } ;
66+ }
67+ }
68+
69+ const Client = z . object ( {
70+ name : z . string ( ) . min ( 1 , "Name is required." ) ,
71+ email : z . email ( "Invalid email format." ) ,
72+ businessName : z . string ( ) . min ( 1 , "Business name is required." ) ,
73+ } ) ;
74+
75+
76+
77+ export const createClientAccount = async ( data : ClientCreationProps ) => {
78+ try {
79+ const parsedData = Client . parse ( data ) ;
80+
81+ const { email, name, businessName} = parsedData ;
82+ if ( ! email || ! name || ! businessName ) {
83+ return {
84+ error : "All fields are required" ,
85+ status : 400 ,
86+ data : null ,
87+ } ;
88+ }
89+ // 1. Check if a user with this email already exists
90+ const existingUser = await db . user . findUnique ( {
91+ where : { email } ,
92+ } ) ;
93+
94+ if ( existingUser ) {
95+ return {
96+ error : "A user with this email already exists." ,
97+ status : 409 ,
98+ data : null ,
99+ } ;
100+ }
101+
102+ const accountActivationToken = randomUUID ( ) ;
103+ const activationTokenExpiry = new Date ( Date . now ( ) + 24 * 60 * 60 * 1000 ) ; // 24 hours from now
104+
105+ // 3. Create the user in your Prisma database
106+ // Password and TraccarId are left as null because they haven't been set yet
107+ const createdUser = await db . user . create ( {
108+ data : {
109+ email,
110+ name,
111+ businessName, // Ensure you add this field to your schema
112+ accountActivationToken,
113+ activationTokenExpiry,
114+ password : null ,
115+ traccarId : null ,
116+ } ,
117+ } ) ;
118+
119+ // 4. Create a corresponding user in Traccar with a temporary password
120+ const temporaryTraccarPassword = randomUUID ( ) ; // Use a strong, random password for Traccar
121+ const traccarUser = await createTraccarUser ( {
122+ name : createdUser . name || "Client User" ,
123+ email : createdUser . email ,
124+ password : temporaryTraccarPassword ,
125+ // The `administrator` flag should be false for regular clients
126+ administrator : false ,
127+ readonly : false ,
128+ } ) ;
129+
130+ // 5. Update the local user with the Traccar ID
131+ const userWithTraccarId = await db . user . update ( {
132+ where : { id : createdUser . id } ,
133+ data : {
134+ traccarId : String ( traccarUser . id ) ,
135+ } ,
136+ } ) ;
137+
138+ // 6. Generate the activation link
139+ const activationLink = `${ process . env . APP_BASE_URL } /activate?token=${ accountActivationToken } ` ;
140+
141+
142+ await sendActivationEmail ( userWithTraccarId . email , activationLink ) ;
143+
144+ return {
145+ error : null ,
146+ status : 201 ,
147+ id : userWithTraccarId . id ,
148+ traccarId : userWithTraccarId . traccarId ,
149+ data : {
150+ message : "Client account created successfully. Please check your email to activate your account." ,
151+ activationLink
152+ } ,
153+
154+ } ;
155+
156+ } catch ( error ) {
157+ console . error ( "Error creating client account:" , error ) ;
158+ if ( error instanceof z . ZodError ) {
159+ return {
160+ error : "Invalid input data" ,
161+ status : 400 ,
162+ data : null ,
163+ } ;
164+ }
165+
166+ return {
167+ error : "An unexpected error occurred. Please try again" ,
168+ status : 500 ,
169+ data : null ,
170+ }
171+ }
172+ }
173+
174+
175+
176+
177+ // Define a type for custom attributes (simple string keys and string values for now)
178+ type CustomDeviceAttributes = {
179+ licensePlate ?: string ;
180+ make ?: string ;
181+ model ?: string ;
182+ year ?: string ;
183+ color ?: string ;
184+ // Add more as needed
185+ [ key : string ] : string | undefined ; // Allow for other arbitrary string attributes
186+ } ;
187+
188+
189+
190+
191+ // Updated Schema for creating a device, now including attributes
192+ const CreateDeviceSchema = z . object ( {
193+ name : z . string ( ) . min ( 1 , "Device name is required." ) ,
194+ uniqueId : z . string ( ) . min ( 1 , "Unique ID (IMEI) is required." ) ,
195+ userId : z . number ( ) . int ( ) . optional ( ) , // This is the local Prisma User ID
196+ // New: Optional attributes for the device (vehicle data)
197+ attributes : z . object ( {
198+ licensePlate : z . string ( ) . optional ( ) ,
199+ make : z . string ( ) . optional ( ) ,
200+ model : z . string ( ) . optional ( ) ,
201+ year : z . string ( ) . optional ( ) ,
202+ color : z . string ( ) . optional ( ) ,
203+ // Add other specific attributes you want to validate here
204+ } ) . optional ( ) ,
205+ } ) ;
206+
207+ type CreateDeviceProps = z . infer < typeof CreateDeviceSchema > ;
208+
209+ export async function createDevice ( data : CreateDeviceProps ) {
210+ try {
211+ const parsedData = CreateDeviceSchema . parse ( data ) ;
212+ const { name, uniqueId, userId, attributes } = parsedData ; // Destructure attributes
213+
214+ // 1. Check if a device with this uniqueId already exists locally
215+ const existingLocalDevice = await db . device . findUnique ( {
216+ where : { uniqueId } ,
217+ } ) ;
218+
219+ if ( existingLocalDevice ) {
220+ return {
221+ error : "A device with this Unique ID already exists in your system." ,
222+ status : 409 ,
223+ data : null ,
224+ } ;
225+ }
226+
227+ // Determine the Traccar user ID if a local userId is provided
228+ let traccarUserId : string | undefined = undefined ;
229+ if ( userId !== undefined && userId !== null ) {
230+ const user = await db . user . findUnique ( {
231+ where : { id : userId } ,
232+ select : { traccarId : true } ,
233+ } ) ;
234+ if ( user ?. traccarId ) {
235+ traccarUserId = String ( user . traccarId ) ;
236+ } else {
237+ console . warn ( `Local user ID ${ userId } found but has no associated Traccar ID. Device will be created unassigned in Traccar.` ) ;
238+ }
239+ }
240+
241+ // 2. Create the device in Traccar
242+ // The 'userId' property is NOT sent directly in the POST /devices payload.
243+ // The association is made via the /permissions endpoint.
244+ const traccarDevice = await callTraccarAdminApi (
245+ 'devices' , // Endpoint for creating devices in Traccar
246+ 'POST' , // HTTP method for creation
247+ {
248+ name,
249+ uniqueId,
250+ status : "offline" , // Default status for new devices in Traccar
251+ disabled : false , // Default disabled status
252+ attributes : attributes || { } , // Include the custom attributes for the vehicle
253+ }
254+ ) ;
255+
256+ if ( ! traccarDevice ?. id ) {
257+ throw new Error ( "Traccar did not return a valid device ID after creation." ) ;
258+ }
259+
260+ // 3. Link the device to the user in Traccar via permissions if a userId was provided
261+ if ( traccarUserId ) {
262+ try {
263+ await callTraccarAdminApi (
264+ 'permissions' ,
265+ 'POST' ,
266+ {
267+ userId : parseInt ( traccarUserId ) ,
268+ deviceId : traccarDevice . id , // )
269+ }
270+ ) ;
271+ console . log ( `Permission created for Traccar device ${ traccarDevice . id } to Traccar user ${ traccarUserId } .` ) ;
272+ } catch ( permissionError ) {
273+ console . error ( `Failed to create permission for Traccar device ${ traccarDevice . id } and user ${ traccarUserId } :` , permissionError ) ;
274+ // Decide how to handle this. For now, we'll log and still create locally,
275+ // but the user might not see it in Traccar UI. A more strict approach
276+ // would involve deleting the device from Traccar if permission fails.
277+ }
278+ }
279+
280+
281+ // 4. Create the device record in your local Prisma database
282+ const createdLocalDevice = await db . device . create ( {
283+ data : {
284+ name,
285+ uniqueId,
286+ traccarId : String ( traccarDevice . id ) , // Store Traccar's internal Device ID
287+ userId : userId , // Link to your local Prisma User ID
288+ attributes : ( attributes || { } ) as Prisma . InputJsonValue , // Store the attributes locally as JSON
289+ } ,
290+ } ) ;
291+
292+ return {
293+ error : null ,
294+ status : 201 ,
295+ data : {
296+ message : "Device created and linked successfully!" ,
297+ device : createdLocalDevice ,
298+ } ,
299+ } ;
300+
301+ } catch ( error ) {
302+ console . error ( "Error creating device:" , error ) ;
303+ if ( error instanceof z . ZodError ) {
304+ const formattedErrors = error . message ;
305+ return {
306+ error : `Invalid input data: ${ formattedErrors } ` ,
307+ status : 400 ,
308+ data : null ,
309+ } ;
310+ }
311+ return {
312+ error : "An unexpected error occurred while creating the device. Please try again." ,
313+ status : 500 ,
314+ data : null ,
315+ } ;
316+ }
317+ }
318+
319+
320+
321+
322+
323+
324+ export async function deleteDevice ( deviceId : number ) {
325+ try {
326+ // 1. Fetch the local device to get its Traccar ID
327+ const localDevice = await db . device . findUnique ( {
328+ where : { id : deviceId } ,
329+ select : { id : true , traccarId : true , name : true } ,
330+ } ) ;
331+
332+ if ( ! localDevice ) {
333+ return {
334+ success : false ,
335+ error : "Device not found in local database." ,
336+ status : 404 ,
337+ } ;
338+ }
339+
340+ // 2. Delete the device from Traccar
341+ // Traccar API endpoint for deleting a device is DELETE /api/devices/{id}
342+ if ( localDevice . traccarId ) {
343+ try {
344+ await callTraccarAdminApi (
345+ `devices/${ localDevice . traccarId } ` , // Endpoint for specific device
346+ 'DELETE' // HTTP method for deletion
347+ ) ;
348+ console . log ( `Device ${ localDevice . name } (Traccar ID: ${ localDevice . traccarId } ) successfully deleted from Traccar.` ) ;
349+ } catch ( traccarError ) {
350+ console . error ( `Failed to delete device ${ localDevice . name } from Traccar (internal log):` , traccarError ) ;
351+ // Abort local DB deletion and return a generic error message
352+ return {
353+ success : false ,
354+ error : "An issue occurred during device deletion from Traccar. Please try again." ,
355+ status : 500 ,
356+ } ;
357+ }
358+ } else {
359+ console . warn ( `Device ${ localDevice . name } has no Traccar ID. Skipping Traccar deletion.` ) ;
360+ }
361+
362+ // 3. Delete the device record from your local Prisma database
363+ await db . device . delete ( {
364+ where : { id : deviceId } ,
365+ } ) ;
366+
367+ return {
368+ success : true ,
369+ error : null ,
370+ status : 200 ,
371+ message : "Device deleted successfully!" ,
372+ } ;
373+
374+ } catch ( error ) {
375+ console . error ( "Error deleting device:" , error ) ;
376+ return {
377+ success : false ,
378+ error : "An unexpected error occurred while deleting the device. Please try again." ,
379+ status : 500 ,
380+ } ;
381+ }
382+ }
2383
3- export const createCustomerAccount = async ( ) => {
4-
5- }
0 commit comments