Sfoglia il codice sorgente

update vendor, hospital and logs related to sso keycloak

pearlgw 2 mesi fa
parent
commit
87523a065b

+ 3 - 3
src/controllers/admin/VendorController.js

@@ -14,7 +14,7 @@ exports.getAllVendor = async (req, res) => {
14 14
             page, limit, search, sortBy, orderBy
15 15
         });
16 16
 
17
-        return VendorCollection(req, res, vendors, total, page, limit, 'Vendor data successfully retrieved');
17
+        return VendorCollection({ req, res, data: vendors, total, page, limit, message: 'Vendor data successfully retrieved' });
18 18
     } catch (err) {
19 19
         return errorResponse(res, err);
20 20
     }
@@ -23,8 +23,8 @@ exports.getAllVendor = async (req, res) => {
23 23
 exports.showVendor = async (req, res) => {
24 24
     try {
25 25
         const id = req.params.id;
26
-        const data = await vendorService.showVendorService(id);
27
-        return VendorResource(res, data, 'Success show vendor');
26
+        const { vendor, userName } = await vendorService.showVendorService(id);
27
+        return VendorResource(res, vendor, userName, 'Success show vendor');
28 28
     } catch (err) {
29 29
         return errorResponse(res, err);
30 30
     }

+ 2 - 18
src/repository/admin/VendorRepository.js

@@ -15,15 +15,7 @@ const VendorRepository = {
15 15
                 strengths: true,
16 16
                 weaknesses: true,
17 17
                 website: true,
18
-                user: {
19
-                    select: {
20
-                        id: true,
21
-                        username: true,
22
-                        email: true,
23
-                        firstname: true,
24
-                        lastname: true,
25
-                    }
26
-                },
18
+                created_by: true,
27 19
                 _count: {
28 20
                     select: {
29 21
                         vendor_histories: {
@@ -66,15 +58,7 @@ const VendorRepository = {
66 58
                 strengths: true,
67 59
                 weaknesses: true,
68 60
                 website: true,
69
-                user: {
70
-                    select: {
71
-                        id: true,
72
-                        username: true,
73
-                        email: true,
74
-                        firstname: true,
75
-                        lastname: true,
76
-                    }
77
-                },
61
+                created_by: true,
78 62
                 _count: {
79 63
                     select: {
80 64
                         vendor_histories: {

+ 19 - 15
src/resources/admin/vendor/VendorCollection.js

@@ -1,22 +1,26 @@
1 1
 const { ListResponse } = require("../../../utils/ListResponse");
2 2
 const { formatISOWithoutTimezone } = require("../../../utils/FormatDate.js");
3
+const { getUserNameById } = require("../../../utils/CheckUserKeycloak");
3 4
 
4
-const formatItem = (item) => ({
5
-    ...item,
6
-    createdAt: formatISOWithoutTimezone(item.createdAt),
7
-    updatedAt: formatISOWithoutTimezone(item.updatedAt)
8
-});
5
+const transformVendorList = async (data = []) => {
6
+    return Promise.all(data.map(async ({ created_by, ...rest }) => {
7
+        const name = await getUserNameById(created_by);
9 8
 
10
-exports.VendorCollection = (req, res, data = [], total = null, page = 1, limit = 10, message = 'Success') => {
11
-    const formattedData = data.map(formatItem);
9
+        return {
10
+            ...rest,
11
+            user: {
12
+                id: created_by,
13
+                name: name
14
+            },
15
+            createdAt: formatISOWithoutTimezone(rest.createdAt),
16
+            updatedAt: formatISOWithoutTimezone(rest.updatedAt)
17
+        };
18
+    }));
19
+};
12 20
 
13
-    if (typeof total !== 'number') {
14
-        return res.status(200).json({
15
-            success: true,
16
-            message,
17
-            data: Array.isArray(formattedData)
18
-        });
19
-    }
21
+// Collection yang async
22
+exports.VendorCollection = async ({ req, res, data = [], total = 0, page = 1, limit = 10, message = 'Success' }) => {
23
+    const formatted = await transformVendorList(data);
20 24
 
21
-    return ListResponse({ req, res, data: formattedData, total, page, limit, message });
25
+    return ListResponse({ req, res, data: formatted, total, page, limit, message });
22 26
 };

+ 16 - 8
src/resources/admin/vendor/VendorResource.js

@@ -1,17 +1,25 @@
1 1
 const { formatISOWithoutTimezone } = require("../../../utils/FormatDate");
2 2
 
3
-const formatItem = (item) => ({
4
-    ...item,
5
-    createdAt: formatISOWithoutTimezone(item.createdAt),
6
-    updatedAt: formatISOWithoutTimezone(item.updatedAt)
7
-});
3
+const formatItem = ({ item, userName }) => {
4
+    const { created_by, ...rest } = item;
8 5
 
9
-exports.VendorResource = (res, data, message = 'Success') => {
10
-    const formattedData = formatItem(data);
6
+    return {
7
+        ...rest,
8
+        user: {
9
+            id: created_by,
10
+            name: userName
11
+        },
12
+        createdAt: formatISOWithoutTimezone(item.createdAt),
13
+        updatedAt: formatISOWithoutTimezone(item.updatedAt)
14
+    };
15
+};
16
+
17
+exports.VendorResource = (res, item, userName, message = 'Success') => {
18
+    const formattedData = formatItem({ item, userName });
11 19
 
12 20
     return res.status(200).json({
13 21
         success: true,
14 22
         message,
15 23
         data: formattedData
16 24
     });
17
-};
25
+};

+ 1 - 1
src/services/admin/HospitalService.js

@@ -40,7 +40,7 @@ exports.showHospitalService = async (id) => {
40 40
 };
41 41
 
42 42
 exports.storeHospitalService = async (validateData, req) => {
43
-    const creatorId = req.user.id;
43
+    const creatorId = req.tokenData.sub;
44 44
     const province = await ProvinceRepository.findById(validateData.province_id);
45 45
     if (!province) {
46 46
         throw new HttpException('Province not found', 404);

+ 7 - 4
src/services/admin/VendorService.js

@@ -5,6 +5,7 @@ const { SearchFilter } = require('../../utils/SearchFilter.js');
5 5
 const timeLocal = require('../../utils/TimeLocal.js');
6 6
 const { createLog, updateLog, deleteLog } = require('../../utils/LogActivity.js');
7 7
 const { formatISOWithoutTimezone } = require('../../utils/FormatDate.js');
8
+const { getUserNameById } = require('../../utils/CheckUserKeycloak.js');
8 9
 
9 10
 exports.getAllVendorService = async ({ page, limit, search, sortBy, orderBy }) => {
10 11
     const skip = (page - 1) * limit;
@@ -28,11 +29,13 @@ exports.showVendorService = async (id) => {
28 29
         throw new HttpException("Data vendor not found", 404);
29 30
     }
30 31
 
31
-    return vendor;
32
+    const userName = await getUserNameById(vendor.created_by);
33
+
34
+    return { vendor, userName };
32 35
 };
33 36
 
34 37
 exports.storeVendorService = async (validateData, req) => {
35
-    const creatorId = req.user.id;
38
+    const creatorId = req.tokenData.sub;
36 39
 
37 40
     const name_vendor = await prisma.vendor.findFirst({
38 41
         where: {
@@ -55,7 +58,7 @@ exports.storeVendorService = async (validateData, req) => {
55 58
 };
56 59
 
57 60
 exports.updateVendorService = async (validateData, id, req) => {
58
-    const creatorId = req.user.id;
61
+    // const creatorId = req.user.id;
59 62
 
60 63
     const vendor = await VendorRepository.findById(id);
61 64
     if (!vendor) {
@@ -77,7 +80,7 @@ exports.updateVendorService = async (validateData, id, req) => {
77 80
 
78 81
     const payload = {
79 82
         ...validateData,
80
-        created_by: creatorId
83
+        // created_by: creatorId
81 84
     };
82 85
 
83 86
     const data = await VendorRepository.update(id, payload);

+ 102 - 19
src/services/sales/HospitalService.js

@@ -94,7 +94,7 @@ exports.getAllHospitalByAreaService = async ({ page, limit, search, sortBy, orde
94 94
 // };
95 95
 
96 96
 exports.storeHospitalService = async (validateData, req) => {
97
-    const creatorId = req.user.id;
97
+    const creatorId = req.tokenData.sub;
98 98
 
99 99
     const province = await ProvinceRepository.findById(validateData.province_id);
100 100
     if (!province) {
@@ -103,7 +103,7 @@ exports.storeHospitalService = async (validateData, req) => {
103 103
 
104 104
     const userArea = await prisma.userArea.findFirst({
105 105
         where: {
106
-            user_id: req.user.id,
106
+            user_id: req.tokenData.sub,
107 107
             province_id: validateData.province_id
108 108
         }
109 109
     });
@@ -135,12 +135,45 @@ exports.storeHospitalService = async (validateData, req) => {
135 135
 
136 136
     const imagePath = req.file ? `/storage/img/${req.file.filename}` : null;
137 137
 
138
+    let latitude = validateData.latitude ?? null;
139
+    let longitude = validateData.longitude ?? null;
140
+    let gmapsUrl = validateData.gmaps_url ?? null;
141
+
142
+    if (gmapsUrl) {
143
+        if (gmapsUrl.includes("www.google.com/maps")) {
144
+            const regex = /@(-?\d+\.\d+),(-?\d+\.\d+)/;
145
+            const match = gmapsUrl.match(regex);
146
+
147
+            if (match) {
148
+                latitude = parseFloat(match[1]);
149
+                longitude = parseFloat(match[2]);
150
+            } else {
151
+                throw new HttpException("Unable to extract coordinates from gmaps_url", 400);
152
+            }
153
+
154
+        } else if (gmapsUrl.includes("maps.app.goo.gl")) {
155
+            latitude = null;
156
+            longitude = null;
157
+
158
+        } else {
159
+            // URL disediakan tapi bukan dari domain yang valid
160
+            throw new HttpException("gmaps_url must be a valid Google Maps URL", 400);
161
+        }
162
+    } else if (latitude !== null && longitude !== null) {
163
+        gmapsUrl = null;
164
+    } else {
165
+        throw new HttpException("Either gmaps_url or coordinates must be provided", 400);
166
+    }
167
+
138 168
     const payload = {
139 169
         ...validateData,
140 170
         image: imagePath,
141 171
         progress_status: "cari_data",
142 172
         simrs_type: "-",
143
-        created_by: creatorId
173
+        created_by: creatorId,
174
+        latitude,
175
+        longitude,
176
+        gmaps_url: gmapsUrl,
144 177
     };
145 178
 
146 179
     const data = await salesHospitalRepository.create(payload);
@@ -157,7 +190,7 @@ exports.updateHospitalService = async (validateData, id, req) => {
157 190
 
158 191
     const userArea = await prisma.userArea.findFirst({
159 192
         where: {
160
-            user_id: req.user.id,
193
+            user_id: req.tokenData.sub,
161 194
             province_id: validateData.province_id
162 195
         }
163 196
     });
@@ -166,14 +199,18 @@ exports.updateHospitalService = async (validateData, id, req) => {
166 199
         throw new HttpException("You are not authorized to update hospital in this province", 403);
167 200
     }
168 201
 
169
-    const province = await ProvinceRepository.findById(validateData.province_id);
170
-    if (!province) {
171
-        throw new HttpException('Province not found', 404);
202
+    if (validateData.province_id) {
203
+        const province = await ProvinceRepository.findById(validateData.province_id);
204
+        if (!province) {
205
+            throw new HttpException('Province not found', 404);
206
+        }
172 207
     }
173 208
 
174
-    const city = await CityRepository.findById(validateData.city_id);
175
-    if (!city) {
176
-        throw new HttpException('City not found', 404);
209
+    if (validateData.city_id) {
210
+        const city = await CityRepository.findById(validateData.city_id);
211
+        if (!city) {
212
+            throw new HttpException('City not found', 404);
213
+        }
177 214
     }
178 215
 
179 216
     if (validateData.progress_status && !validProgressStatuses.includes(validateData.progress_status)) {
@@ -183,16 +220,19 @@ exports.updateHospitalService = async (validateData, id, req) => {
183 220
         );
184 221
     }
185 222
 
186
-    const existingHospital = await prisma.hospital.findFirst({
187
-        where: {
188
-            name: validateData.name,
189
-            city_id: validateData.city_id,
190
-            deletedAt: null
223
+    if (validateData.name && validateData.city_id) {
224
+        const existingHospital = await prisma.hospital.findFirst({
225
+            where: {
226
+                name: validateData.name,
227
+                city_id: validateData.city_id,
228
+                deletedAt: null,
229
+                NOT: { id }
230
+            }
231
+        });
232
+
233
+        if (existingHospital) {
234
+            throw new HttpException('Hospital with same name in this city already exists', 400);
191 235
         }
192
-    });
193
-
194
-    if (existingHospital) {
195
-        throw new HttpException('Hospital with same name in this city already exists', 400);
196 236
     }
197 237
 
198 238
     // Jika ada file baru, replace image
@@ -201,10 +241,53 @@ exports.updateHospitalService = async (validateData, id, req) => {
201 241
         imagePath = `/storage/img/${req.file.filename}`; // path relatif
202 242
     }
203 243
 
244
+    // Handle koordinat dan gmaps_url
245
+    let latitude = hospital.latitude;
246
+    let longitude = hospital.longitude;
247
+    let gmapsUrl = hospital.gmaps_url;
248
+
249
+    if (
250
+        validateData.latitude !== undefined &&
251
+        validateData.longitude !== undefined &&
252
+        validateData.latitude !== null &&
253
+        validateData.longitude !== null
254
+    ) {
255
+        // Jika diberikan lat long langsung
256
+        latitude = validateData.latitude;
257
+        longitude = validateData.longitude;
258
+        gmapsUrl = validateData.gmaps_url || gmapsUrl;
259
+    } else if (
260
+        validateData.gmaps_url &&
261
+        typeof validateData.gmaps_url === "string" &&
262
+        validateData.gmaps_url.trim() !== ""
263
+    ) {
264
+        gmapsUrl = validateData.gmaps_url;
265
+
266
+        if (gmapsUrl.includes("www.google.com/maps")) {
267
+            const regex = /@(-?\d+\.\d+),(-?\d+\.\d+)/;
268
+            const match = gmapsUrl.match(regex);
269
+            if (match) {
270
+                latitude = parseFloat(match[1]);
271
+                longitude = parseFloat(match[2]);
272
+            } else {
273
+                throw new HttpException("Unable to extract coordinates from gmaps_url", 400);
274
+            }
275
+        } else if (gmapsUrl.includes("maps.app.goo.gl")) {
276
+            // Tidak bisa ambil koordinat langsung
277
+            latitude = null;
278
+            longitude = null;
279
+        } else {
280
+            throw new HttpException("gmaps_url must be a valid Google Maps URL", 400);
281
+        }
282
+    }
283
+
204 284
     const payload = {
205 285
         ...validateData,
206 286
         image: imagePath,
207 287
         // created_by: req.user.id,
288
+        latitude,
289
+        longitude,
290
+        gmaps_url: gmapsUrl,
208 291
     };
209 292
 
210 293
     const data = await salesHospitalRepository.update(id, payload);

+ 1 - 1
src/services/sales/VendorService.js

@@ -5,7 +5,7 @@ const { SearchFilter } = require('../../utils/SearchFilter.js');
5 5
 
6 6
 exports.getAllVendorService = async ({ page, limit, search, sortBy, orderBy }, req) => {
7 7
     const skip = (page - 1) * limit;
8
-    const userId = req.user.id;
8
+    const userId = req.tokenData.sub;
9 9
 
10 10
     // 1. Ambil provinsi yang menjadi area kerja sales
11 11
     const userAreas = await prisma.userArea.findMany({

+ 66 - 29
src/utils/LogActivity.js

@@ -1,43 +1,80 @@
1 1
 const jwt = require('jsonwebtoken');
2 2
 const prisma = require('../prisma/PrismaClient.js');
3
-const timeLocal = require('../utils/TimeLocal.js')
3
+const timeLocal = require('../utils/TimeLocal.js');
4
+const { getUserNameById } = require('./CheckUserKeycloak.js');
4 5
 
5
-const baseLog = async ({ req, action, data }) => {
6
-    // try {
7
-    //     const authHeader = req.headers.authorization;
8
-    //     if (!authHeader || !authHeader.startsWith('Bearer ')) return;
6
+// const baseLog = async ({ req, action, data }) => {
7
+// 1
8
+// try {
9
+//     const authHeader = req.headers.authorization;
10
+//     if (!authHeader || !authHeader.startsWith('Bearer ')) return;
11
+
12
+//     const token = authHeader.split(' ')[1];
13
+//     const decoded = jwt.decode(token);
14
+//     if (!decoded?.id || !decoded?.username) return;
15
+
16
+//     await prisma.activityLog.create({
17
+//         data: {
18
+//             user_id: decoded.id,
19
+//             username: decoded.username,
20
+//             action: JSON.stringify({ [action]: data }),
21
+//             createdAt: timeLocal.now().toDate(),
22
+//             updatedAt: timeLocal.now().toDate(),
23
+//             deletedAt: null,
24
+//         }
25
+//     });
26
+// } catch (err) {
27
+//     console.error('Failed to log activity:', err.message);
28
+// }
29
+
30
+// 2
31
+// try {
32
+//     let userId, username;
9 33
 
10
-    //     const token = authHeader.split(' ')[1];
11
-    //     const decoded = jwt.decode(token);
12
-    //     if (!decoded?.id || !decoded?.username) return;
34
+//     // Coba ambil dari Bearer token jika ada
35
+//     const authHeader = req?.headers?.authorization;
36
+//     if (authHeader?.startsWith('Bearer ')) {
37
+//         const token = authHeader.split(' ')[1];
38
+//         const decoded = jwt.decode(token);
39
+//         userId = decoded?.id;
40
+//         username = decoded?.username;
41
+//     }
13 42
 
14
-    //     await prisma.activityLog.create({
15
-    //         data: {
16
-    //             user_id: decoded.id,
17
-    //             username: decoded.username,
18
-    //             action: JSON.stringify({ [action]: data }),
19
-    //             createdAt: timeLocal.now().toDate(),
20
-    //             updatedAt: timeLocal.now().toDate(),
21
-    //             deletedAt: null,
22
-    //         }
23
-    //     });
24
-    // } catch (err) {
25
-    //     console.error('Failed to log activity:', err.message);
26
-    // }
43
+//     // Fallback ke data langsung jika belum dapat dari token
44
+//     if (!userId || !username) {
45
+//         userId = data?.id;
46
+//         username = data?.username;
47
+//     }
27 48
 
49
+//     if (!userId || !username) return;
50
+
51
+//     await prisma.activityLog.create({
52
+//         data: {
53
+//             user_id: userId,
54
+//             username,
55
+//             action: JSON.stringify({ [action]: data }),
56
+//             createdAt: timeLocal.now().toDate(),
57
+//             updatedAt: timeLocal.now().toDate(),
58
+//             deletedAt: null,
59
+//         }
60
+//     });
61
+// } catch (err) {
62
+//     console.error('Failed to log activity:', err.message);
63
+// }
64
+
65
+// };
66
+// 3
67
+const baseLog = async ({ req, action, data }) => {
28 68
     try {
29 69
         let userId, username;
30 70
 
31
-        // Coba ambil dari Bearer token jika ada
32
-        const authHeader = req?.headers?.authorization;
33
-        if (authHeader?.startsWith('Bearer ')) {
34
-            const token = authHeader.split(' ')[1];
35
-            const decoded = jwt.decode(token);
36
-            userId = decoded?.id;
37
-            username = decoded?.username;
71
+        // Ambil userId dari token Keycloak
72
+        if (req?.tokenData?.sub) {
73
+            userId = req.tokenData.sub;
74
+            username = await getUserNameById(userId);
38 75
         }
39 76
 
40
-        // Fallback ke data langsung jika belum dapat dari token
77
+        // Fallback kalau tokenData tidak ada
41 78
         if (!userId || !username) {
42 79
             userId = data?.id;
43 80
             username = data?.username;