Skip to content

Commit 0b08f40

Browse files
committed
solved device creation bug
1 parent 63a30a0 commit 0b08f40

File tree

33 files changed

+1597
-337
lines changed

33 files changed

+1597
-337
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
3434
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
3535

3636
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37+
38+
39+
<!-- HOW TO RUN TRACCAR ON THE SERVER -->
40+
java -jar tracker-server.jar conf/traccar.xml

actions/admin-actions.ts

Lines changed: 382 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,383 @@
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

Comments
 (0)