strapi-plugin-magic-mark 1.3.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  const crypto = require("crypto");
3
3
  const os = require("os");
4
+ const fs = require("fs");
5
+ const path = require("path");
4
6
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
5
7
  const crypto__default = /* @__PURE__ */ _interopDefault(crypto);
6
8
  const os__default = /* @__PURE__ */ _interopDefault(os);
@@ -36,7 +38,7 @@ const bootstrap = ({ strapi }) => {
36
38
  const licenseStatus = await licenseGuardService2.initialize();
37
39
  if (!licenseStatus.valid) {
38
40
  strapi.log.error("╔════════════════════════════════════════════════════════════════╗");
39
- strapi.log.error("║ MAGICMARK PLUGIN - NO VALID LICENSE ║");
41
+ strapi.log.error("║ [ERROR] MAGICMARK PLUGIN - NO VALID LICENSE ║");
40
42
  strapi.log.error("║ ║");
41
43
  strapi.log.error("║ This plugin requires a valid license to operate. ║");
42
44
  strapi.log.error("║ Please activate your license via Admin UI: ║");
@@ -52,7 +54,7 @@ const bootstrap = ({ strapi }) => {
52
54
  });
53
55
  const storedKey = await pluginStore.get({ key: "licenseKey" });
54
56
  strapi.log.info("╔════════════════════════════════════════════════════════════════╗");
55
- strapi.log.info("║ MAGICMARK PLUGIN LICENSE ACTIVE ║");
57
+ strapi.log.info("║ [SUCCESS] MAGICMARK PLUGIN LICENSE ACTIVE ║");
56
58
  strapi.log.info("║ ║");
57
59
  if (licenseStatus.data) {
58
60
  strapi.log.info(`║ License: ${licenseStatus.data.licenseKey} ║`);
@@ -63,12 +65,12 @@ const bootstrap = ({ strapi }) => {
63
65
  strapi.log.info(`║ Status: Grace Period Active ║`);
64
66
  }
65
67
  strapi.log.info("║ ║");
66
- strapi.log.info("║ 🔄 Auto-pinging every 15 minutes ║");
68
+ strapi.log.info("║ [PING] Auto-pinging every 15 minutes ║");
67
69
  strapi.log.info("╚════════════════════════════════════════════════════════════════╝");
68
70
  }
69
71
  }, 3e3);
70
72
  } catch (error) {
71
- strapi.log.error(" Error initializing License Guard:", error);
73
+ strapi.log.error("[ERROR] Error initializing License Guard:", error);
72
74
  }
73
75
  strapi.log.info("[Magic-Mark] Plugin bootstrapped successfully");
74
76
  };
@@ -77,10 +79,10 @@ const destroy = ({ strapi }) => {
77
79
  const licenseGuardService2 = strapi.plugin("magic-mark")?.service("license-guard");
78
80
  if (licenseGuardService2) {
79
81
  licenseGuardService2.cleanup();
80
- strapi.log.info(" License Guard cleanup completed");
82
+ strapi.log.info("[SUCCESS] License Guard cleanup completed");
81
83
  }
82
84
  } catch (error) {
83
- strapi.log.error(" Error during License Guard cleanup:", error);
85
+ strapi.log.error("[ERROR] Error during License Guard cleanup:", error);
84
86
  }
85
87
  };
86
88
  const config = {
@@ -129,9 +131,9 @@ const contentTypes = {
129
131
  },
130
132
  emoji: {
131
133
  type: "string",
132
- default: "🔖",
134
+ default: "bookmark",
133
135
  configurable: false,
134
- maxLength: 2
136
+ maxLength: 50
135
137
  },
136
138
  isPinned: {
137
139
  type: "boolean",
@@ -147,17 +149,30 @@ const contentTypes = {
147
149
  type: "text",
148
150
  configurable: false
149
151
  },
150
- // Array of role IDs that have access to this bookmark
152
+ // Creator's documentId (stored as string, not relation)
153
+ // We store documentId instead of relation to avoid admin::user permission issues
154
+ creatorId: {
155
+ type: "string",
156
+ configurable: false,
157
+ description: "documentId of the admin user who created this bookmark"
158
+ },
159
+ // Last updater's documentId
160
+ updaterId: {
161
+ type: "string",
162
+ configurable: false,
163
+ description: "documentId of the admin user who last updated this bookmark"
164
+ },
165
+ // Array of role documentIds that have access to this bookmark
151
166
  sharedWithRoles: {
152
167
  type: "json",
153
168
  configurable: false,
154
- description: "Array of role IDs that have access to this bookmark"
169
+ description: "Array of role documentIds that have access to this bookmark"
155
170
  },
156
- // Array of user IDs that have direct access to this bookmark
171
+ // Array of user documentIds that have direct access to this bookmark
157
172
  sharedWithUsers: {
158
173
  type: "json",
159
174
  configurable: false,
160
- description: "Array of user IDs that have direct access to this bookmark"
175
+ description: "Array of user documentIds that have direct access to this bookmark"
161
176
  },
162
177
  // Whether this bookmark is public to all admin users
163
178
  isPublic: {
@@ -170,14 +185,21 @@ const contentTypes = {
170
185
  }
171
186
  }
172
187
  };
188
+ const ADMIN_ROLE_UID = "admin::role";
189
+ const ADMIN_USER_UID$1 = "admin::user";
173
190
  const bookmarkController = ({ strapi }) => ({
191
+ /**
192
+ * Get all bookmarks for current user
193
+ * GET /magic-mark/bookmarks
194
+ */
174
195
  async getAll(ctx) {
175
196
  try {
176
197
  const user = ctx.state.user;
177
198
  if (!user) {
178
199
  return ctx.throw(401, "User not authenticated");
179
200
  }
180
- const bookmarks = await strapi.plugin("magic-mark").service("bookmarks").findAll(user.id);
201
+ const userId = user.documentId || String(user.id);
202
+ const bookmarks = await strapi.plugin("magic-mark").service("bookmarks").findAll(userId);
181
203
  ctx.body = {
182
204
  data: bookmarks,
183
205
  meta: {
@@ -188,21 +210,26 @@ const bookmarkController = ({ strapi }) => ({
188
210
  ctx.throw(500, `Error fetching bookmarks: ${error}`);
189
211
  }
190
212
  },
213
+ /**
214
+ * Create a new bookmark
215
+ * POST /magic-mark/bookmarks
216
+ */
191
217
  async create(ctx) {
192
218
  try {
193
219
  const user = ctx.state.user;
194
220
  if (!user) {
195
221
  return ctx.throw(401, "User not authenticated");
196
222
  }
197
- const { name, path, query, emoji, description, sharedWithRoles, sharedWithUsers, isPublic } = ctx.request.body;
223
+ const { name, path: path2, query, emoji, description, sharedWithRoles, sharedWithUsers, isPublic } = ctx.request.body;
198
224
  if (!name || name.trim().length === 0) {
199
225
  return ctx.throw(400, "Name is required");
200
226
  }
201
- if (!path || path.trim().length === 0) {
227
+ if (!path2 || path2.trim().length === 0) {
202
228
  return ctx.throw(400, "Path is required");
203
229
  }
230
+ const userId = user.documentId || String(user.id);
204
231
  const service = strapi.plugin("magic-mark").service("bookmarks");
205
- const bookmark = await service.create(name, path, query, emoji, description, user.id, sharedWithRoles, sharedWithUsers, isPublic);
232
+ const bookmark = await service.create(name, path2, query, emoji, description, userId, sharedWithRoles, sharedWithUsers, isPublic);
206
233
  ctx.body = {
207
234
  data: bookmark
208
235
  };
@@ -210,6 +237,10 @@ const bookmarkController = ({ strapi }) => ({
210
237
  ctx.throw(500, `Error creating bookmark: ${error}`);
211
238
  }
212
239
  },
240
+ /**
241
+ * Update an existing bookmark
242
+ * PUT /magic-mark/bookmarks/:id
243
+ */
213
244
  async update(ctx) {
214
245
  try {
215
246
  const user = ctx.state.user;
@@ -217,14 +248,15 @@ const bookmarkController = ({ strapi }) => ({
217
248
  return ctx.throw(401, "User not authenticated");
218
249
  }
219
250
  const { id } = ctx.params;
220
- const { name, path, query, emoji, description, isPinned, order, sharedWithRoles, sharedWithUsers, isPublic } = ctx.request.body;
251
+ const { name, path: path2, query, emoji, description, isPinned, order, sharedWithRoles, sharedWithUsers, isPublic } = ctx.request.body;
221
252
  if (!name || name.trim().length === 0) {
222
253
  return ctx.throw(400, "Name is required");
223
254
  }
224
- if (!path || path.trim().length === 0) {
255
+ if (!path2 || path2.trim().length === 0) {
225
256
  return ctx.throw(400, "Path is required");
226
257
  }
227
- const bookmark = await strapi.plugin("magic-mark").service("bookmarks").update(id, { name, path, query, emoji, description, isPinned, order, sharedWithRoles, sharedWithUsers, isPublic }, user.id);
258
+ const userId = user.documentId || String(user.id);
259
+ const bookmark = await strapi.plugin("magic-mark").service("bookmarks").update(id, { name, path: path2, query, emoji, description, isPinned, order, sharedWithRoles, sharedWithUsers, isPublic }, userId);
228
260
  ctx.body = {
229
261
  data: bookmark
230
262
  };
@@ -232,6 +264,10 @@ const bookmarkController = ({ strapi }) => ({
232
264
  ctx.throw(500, `Error updating bookmark: ${error}`);
233
265
  }
234
266
  },
267
+ /**
268
+ * Delete a bookmark
269
+ * DELETE /magic-mark/bookmarks/:id
270
+ */
235
271
  async delete(ctx) {
236
272
  try {
237
273
  const user = ctx.state.user;
@@ -247,6 +283,10 @@ const bookmarkController = ({ strapi }) => ({
247
283
  ctx.throw(500, `Error deleting bookmark: ${error}`);
248
284
  }
249
285
  },
286
+ /**
287
+ * Pin or unpin a bookmark
288
+ * PUT /magic-mark/bookmarks/:id/pin
289
+ */
250
290
  async pin(ctx) {
251
291
  try {
252
292
  const user = ctx.state.user;
@@ -255,7 +295,8 @@ const bookmarkController = ({ strapi }) => ({
255
295
  }
256
296
  const { id } = ctx.params;
257
297
  const { isPinned } = ctx.request.body;
258
- const bookmark = await strapi.plugin("magic-mark").service("bookmarks").pin(id, isPinned, user.id);
298
+ const userId = user.documentId || String(user.id);
299
+ const bookmark = await strapi.plugin("magic-mark").service("bookmarks").pin(id, isPinned, userId);
259
300
  ctx.body = {
260
301
  data: bookmark
261
302
  };
@@ -263,6 +304,10 @@ const bookmarkController = ({ strapi }) => ({
263
304
  ctx.throw(500, `Error pinning bookmark: ${error}`);
264
305
  }
265
306
  },
307
+ /**
308
+ * Reorder bookmarks
309
+ * PUT /magic-mark/bookmarks/reorder
310
+ */
266
311
  async reorder(ctx) {
267
312
  try {
268
313
  const user = ctx.state.user;
@@ -273,7 +318,8 @@ const bookmarkController = ({ strapi }) => ({
273
318
  if (!bookmarkIds || !Array.isArray(bookmarkIds)) {
274
319
  return ctx.throw(400, "Bookmark IDs array is required");
275
320
  }
276
- const bookmarks = await strapi.plugin("magic-mark").service("bookmarks").reorder(bookmarkIds, user.id);
321
+ const userId = user.documentId || String(user.id);
322
+ const bookmarks = await strapi.plugin("magic-mark").service("bookmarks").reorder(bookmarkIds, userId);
277
323
  ctx.body = {
278
324
  data: bookmarks
279
325
  };
@@ -281,30 +327,28 @@ const bookmarkController = ({ strapi }) => ({
281
327
  ctx.throw(500, `Error reordering bookmarks: ${error}`);
282
328
  }
283
329
  },
330
+ /**
331
+ * Get all admin roles for sharing
332
+ * GET /magic-mark/roles
333
+ */
284
334
  async getRoles(ctx) {
285
335
  try {
286
336
  const user = ctx.state.user;
287
337
  if (!user) {
288
338
  return ctx.throw(401, "User not authenticated");
289
339
  }
290
- const roles = await strapi.entityService.findMany(
291
- "admin::role",
292
- {
293
- fields: ["id", "name", "code", "description", "createdAt", "updatedAt"],
294
- filters: {},
295
- sort: { name: "asc" },
296
- populate: ["users"]
297
- // Also get user count for each role
298
- }
299
- );
340
+ const roles = await strapi.documents(ADMIN_ROLE_UID).findMany({
341
+ fields: ["name", "code", "description", "createdAt", "updatedAt"],
342
+ sort: { name: "asc" },
343
+ populate: ["users"]
344
+ });
300
345
  const rolesWithDetails = roles?.map((role) => ({
301
- id: role.id,
346
+ id: role.documentId,
302
347
  name: role.name,
303
348
  code: role.code,
304
349
  description: role.description,
305
350
  userCount: role.users?.length || 0,
306
351
  isCustom: !["super_admin", "editor", "author"].includes(role.code)
307
- // Mark custom roles
308
352
  })) || [];
309
353
  ctx.body = {
310
354
  data: {
@@ -315,6 +359,10 @@ const bookmarkController = ({ strapi }) => ({
315
359
  ctx.throw(500, `Error fetching roles: ${error}`);
316
360
  }
317
361
  },
362
+ /**
363
+ * Get all admin users for sharing
364
+ * GET /magic-mark/users
365
+ */
318
366
  async getUsers(ctx) {
319
367
  try {
320
368
  const user = ctx.state.user;
@@ -336,37 +384,36 @@ const bookmarkController = ({ strapi }) => ({
336
384
  users = await adminUserService.find();
337
385
  }
338
386
  } catch (err) {
339
- console.log("[Magic-Mark] Admin service error, trying entity service:", err);
387
+ strapi.log.debug("[magic-mark] Admin service not available, trying Document Service");
340
388
  }
341
389
  if (!users || users.length === 0) {
342
390
  try {
343
- users = await strapi.entityService.findMany("admin::user", {
391
+ users = await strapi.documents(ADMIN_USER_UID$1).findMany({
344
392
  limit: 100
345
393
  });
346
394
  } catch (err) {
347
- console.log("[Magic-Mark] Entity service error:", err);
395
+ strapi.log.debug("[magic-mark] Document Service error for admin::user");
348
396
  }
349
397
  }
350
398
  if (!users || users.length === 0) {
351
399
  try {
352
- users = await strapi.db.query("admin::user").findMany({
400
+ users = await strapi.entityService.findMany(ADMIN_USER_UID$1, {
353
401
  limit: 100
354
402
  });
355
403
  } catch (err) {
356
- console.log("[Magic-Mark] DB query error:", err);
404
+ strapi.log.warn("[magic-mark] entityService fallback also failed");
357
405
  }
358
406
  }
359
- const filteredUsers = users.filter((u) => u.id !== user.id);
360
- console.log("[Magic-Mark] Total admin users found:", users.length);
361
- console.log("[Magic-Mark] Filtered users (excluding current):", filteredUsers.map((u) => ({
362
- id: u.id,
363
- name: `${u.firstname} ${u.lastname}`,
364
- email: u.email
365
- })));
407
+ const currentUserId = user.documentId || String(user.id);
408
+ const filteredUsers = users.filter((u) => {
409
+ const uId = u.documentId || String(u.id);
410
+ return uId !== currentUserId;
411
+ });
412
+ strapi.log.debug(`[magic-mark] Total admin users found: ${users.length}`);
366
413
  ctx.body = {
367
414
  data: {
368
415
  data: filteredUsers.map((u) => ({
369
- id: u.id,
416
+ id: u.documentId || u.id,
370
417
  firstname: u.firstname || "",
371
418
  lastname: u.lastname || "",
372
419
  email: u.email,
@@ -544,16 +591,16 @@ const license = ({ strapi }) => ({
544
591
  const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
545
592
  const verification = await licenseGuard.verifyLicense(trimmedKey);
546
593
  if (!verification.valid) {
547
- strapi.log.warn(`⚠️ Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
594
+ strapi.log.warn(`[WARN] Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
548
595
  return ctx.badRequest("Invalid or expired license key");
549
596
  }
550
597
  const license2 = await licenseGuard.getLicenseByKey(trimmedKey);
551
598
  if (!license2) {
552
- strapi.log.warn(`⚠️ License not found in database: ${trimmedKey.substring(0, 8)}...`);
599
+ strapi.log.warn(`[WARN] License not found in database: ${trimmedKey.substring(0, 8)}...`);
553
600
  return ctx.badRequest("License not found");
554
601
  }
555
602
  if (license2.email.toLowerCase() !== trimmedEmail) {
556
- strapi.log.warn(`⚠️ Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
603
+ strapi.log.warn(`[WARN] Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
557
604
  return ctx.badRequest("Email address does not match this license key");
558
605
  }
559
606
  await licenseGuard.storeLicenseKey(trimmedKey);
@@ -563,7 +610,7 @@ const license = ({ strapi }) => ({
563
610
  pingInterval,
564
611
  data: verification.data
565
612
  };
566
- strapi.log.info(`✅ Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
613
+ strapi.log.info(`[SUCCESS] Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
567
614
  return ctx.send({
568
615
  success: true,
569
616
  message: "License key validated and activated successfully",
@@ -724,12 +771,12 @@ const licenseCheck$1 = (config2, { strapi }) => {
724
771
  });
725
772
  const licenseKey = await pluginStore.get({ key: "licenseKey" });
726
773
  if (!licenseKey) {
727
- strapi.log.warn("⚠️ MagicMark: No license key found");
774
+ strapi.log.warn("[WARN] MagicMark: No license key found");
728
775
  return await next();
729
776
  }
730
777
  const verification = await licenseGuard.verifyLicense(licenseKey, true);
731
778
  if (!verification.valid && !verification.gracePeriod) {
732
- strapi.log.warn("⚠️ MagicMark: Invalid license detected");
779
+ strapi.log.warn("[WARN] MagicMark: Invalid license detected");
733
780
  }
734
781
  return await next();
735
782
  } catch (error) {
@@ -747,25 +794,25 @@ const licenseCheck = async (policyContext, config2, { strapi }) => {
747
794
  const pluginStore = strapi.store({ type: "plugin", name: "magic-mark" });
748
795
  const licenseKey = await pluginStore.get({ key: "licenseKey" });
749
796
  if (!licenseKey) {
750
- strapi.log.warn("⚠️ API access denied: No license key found");
797
+ strapi.log.warn("[WARN] API access denied: No license key found");
751
798
  return policyContext.unauthorized("No license found. Please activate the plugin first.");
752
799
  }
753
800
  const verification = await licenseGuard.verifyLicense(licenseKey);
754
801
  if (!verification.valid) {
755
- strapi.log.warn("⚠️ API access denied: Invalid license");
802
+ strapi.log.warn("[WARN] API access denied: Invalid license");
756
803
  return policyContext.unauthorized("Invalid or expired license. Please check your license status.");
757
804
  }
758
805
  const license2 = await licenseGuard.getLicenseByKey(licenseKey);
759
806
  if (!license2) {
760
- strapi.log.warn("⚠️ API access denied: License not found in database");
807
+ strapi.log.warn("[WARN] API access denied: License not found in database");
761
808
  return policyContext.unauthorized("License not found. Please contact support.");
762
809
  }
763
810
  if (!license2.isActive) {
764
- strapi.log.warn("⚠️ API access denied: License is inactive");
811
+ strapi.log.warn("[WARN] API access denied: License is inactive");
765
812
  return policyContext.unauthorized("License is inactive. Please activate your license.");
766
813
  }
767
814
  if (license2.isExpired) {
768
- strapi.log.warn("⚠️ API access denied: License has expired");
815
+ strapi.log.warn("[WARN] API access denied: License has expired");
769
816
  return policyContext.unauthorized("License has expired. Please renew your license.");
770
817
  }
771
818
  return true;
@@ -777,29 +824,29 @@ const licenseCheck = async (policyContext, config2, { strapi }) => {
777
824
  const policies = {
778
825
  "license-check": licenseCheck
779
826
  };
827
+ const BOOKMARK_UID = "plugin::magic-mark.bookmark";
828
+ const ADMIN_USER_UID = "admin::user";
780
829
  const bookmarkService = ({ strapi }) => ({
830
+ /**
831
+ * Find all bookmarks accessible to a user
832
+ * @param userId - The admin user's documentId
833
+ * @returns Array of accessible bookmarks
834
+ */
781
835
  async findAll(userId) {
782
836
  try {
783
- const user = await strapi.entityService.findOne(
784
- "admin::user",
785
- userId,
786
- {
787
- populate: ["roles"]
788
- }
789
- );
790
- const userRoleIds = user?.roles?.map((role) => role.id) || [];
791
- const allBookmarks = await strapi.entityService.findMany(
792
- "plugin::magic-mark.bookmark",
793
- {
794
- sort: [
795
- { isPinned: "desc" },
796
- { order: "asc" }
797
- ],
798
- populate: ["createdBy"]
799
- }
800
- );
837
+ const user = await strapi.documents(ADMIN_USER_UID).findOne({
838
+ documentId: userId,
839
+ populate: ["roles"]
840
+ });
841
+ const userRoleIds = user?.roles?.map((role) => role.documentId || role.id) || [];
842
+ const allBookmarks = await strapi.documents(BOOKMARK_UID).findMany({
843
+ sort: [
844
+ { isPinned: "desc" },
845
+ { order: "asc" }
846
+ ]
847
+ });
801
848
  const accessibleBookmarks = allBookmarks?.filter((bookmark) => {
802
- if (bookmark.createdBy?.id === userId) {
849
+ if (bookmark.creatorId === userId) {
803
850
  return true;
804
851
  }
805
852
  if (bookmark.isPublic) {
@@ -823,119 +870,140 @@ const bookmarkService = ({ strapi }) => ({
823
870
  throw error;
824
871
  }
825
872
  },
826
- async create(name, path, query, emoji, description, userId, sharedWithRoles, sharedWithUsers, isPublic) {
873
+ /**
874
+ * Create a new bookmark
875
+ * @param name - Bookmark name
876
+ * @param path - URL path
877
+ * @param query - Query string
878
+ * @param emoji - Emoji icon
879
+ * @param description - Description
880
+ * @param userId - Creator's documentId
881
+ * @param sharedWithRoles - Array of role documentIds
882
+ * @param sharedWithUsers - Array of user documentIds
883
+ * @param isPublic - Public visibility
884
+ * @returns Created bookmark
885
+ */
886
+ async create(name, path2, query, emoji, description, userId, sharedWithRoles, sharedWithUsers, isPublic) {
827
887
  try {
828
- const bookmarks = await strapi.entityService.findMany(
829
- "plugin::magic-mark.bookmark",
830
- {
831
- sort: [{ order: "desc" }],
832
- limit: 1
833
- }
834
- );
888
+ const bookmarks = await strapi.documents(BOOKMARK_UID).findMany({
889
+ sort: [{ order: "desc" }],
890
+ limit: 1
891
+ });
835
892
  const maxOrder = bookmarks && bookmarks.length > 0 ? bookmarks[0].order : 0;
836
- const bookmark = await strapi.entityService.create(
837
- "plugin::magic-mark.bookmark",
838
- {
839
- data: {
840
- name,
841
- path,
842
- query: query || "",
843
- emoji: emoji || "🔖",
844
- description,
845
- isPinned: false,
846
- order: (maxOrder || 0) + 1,
847
- createdBy: userId,
848
- sharedWithRoles: sharedWithRoles || [],
849
- sharedWithUsers: sharedWithUsers || [],
850
- isPublic: isPublic || false
851
- }
893
+ const bookmark = await strapi.documents(BOOKMARK_UID).create({
894
+ data: {
895
+ name,
896
+ path: path2,
897
+ query: query || "",
898
+ emoji: emoji || "bookmark",
899
+ description,
900
+ isPinned: false,
901
+ order: (maxOrder || 0) + 1,
902
+ creatorId: userId,
903
+ // Store as string field, not relation
904
+ updaterId: userId,
905
+ sharedWithRoles: sharedWithRoles || [],
906
+ sharedWithUsers: sharedWithUsers || [],
907
+ isPublic: isPublic || false
852
908
  }
853
- );
909
+ });
854
910
  return bookmark;
855
911
  } catch (error) {
856
912
  strapi.log.error("[magic-mark] Error creating bookmark:", error);
857
913
  throw error;
858
914
  }
859
915
  },
860
- async update(id, data, userId) {
916
+ /**
917
+ * Update an existing bookmark
918
+ * @param documentId - Bookmark's documentId
919
+ * @param data - Update data
920
+ * @param userId - Editor's documentId
921
+ * @returns Updated bookmark
922
+ */
923
+ async update(documentId, data, userId) {
861
924
  try {
862
- const existingBookmark = await strapi.entityService.findOne(
863
- "plugin::magic-mark.bookmark",
864
- id,
865
- {
866
- populate: ["createdBy"]
867
- }
868
- );
869
- if (!existingBookmark || existingBookmark.createdBy?.id !== userId) {
925
+ const existingBookmark = await strapi.documents(BOOKMARK_UID).findOne({
926
+ documentId
927
+ });
928
+ if (!existingBookmark || existingBookmark.creatorId !== userId) {
870
929
  throw new Error("Unauthorized: You can only edit your own bookmarks");
871
930
  }
872
- const bookmark = await strapi.entityService.update(
873
- "plugin::magic-mark.bookmark",
874
- id,
875
- {
876
- data: {
877
- name: data.name,
878
- path: data.path,
879
- query: data.query || "",
880
- emoji: data.emoji || "🔖",
881
- description: data.description,
882
- isPinned: data.isPinned,
883
- order: data.order,
884
- sharedWithRoles: data.sharedWithRoles,
885
- sharedWithUsers: data.sharedWithUsers,
886
- isPublic: data.isPublic,
887
- updatedBy: userId
888
- }
931
+ const bookmark = await strapi.documents(BOOKMARK_UID).update({
932
+ documentId,
933
+ data: {
934
+ name: data.name,
935
+ path: data.path,
936
+ query: data.query || "",
937
+ emoji: data.emoji || "bookmark",
938
+ description: data.description,
939
+ isPinned: data.isPinned,
940
+ order: data.order,
941
+ sharedWithRoles: data.sharedWithRoles,
942
+ sharedWithUsers: data.sharedWithUsers,
943
+ isPublic: data.isPublic,
944
+ updaterId: userId
945
+ // Store as string field
889
946
  }
890
- );
947
+ });
891
948
  return bookmark;
892
949
  } catch (error) {
893
950
  strapi.log.error("[magic-mark] Error updating bookmark:", error);
894
951
  throw error;
895
952
  }
896
953
  },
897
- async delete(id) {
954
+ /**
955
+ * Delete a bookmark
956
+ * @param documentId - Bookmark's documentId
957
+ * @returns Deleted bookmark
958
+ */
959
+ async delete(documentId) {
898
960
  try {
899
- return await strapi.entityService.delete(
900
- "plugin::magic-mark.bookmark",
901
- id
902
- );
961
+ return await strapi.documents(BOOKMARK_UID).delete({
962
+ documentId
963
+ });
903
964
  } catch (error) {
904
965
  strapi.log.error("[magic-mark] Error deleting bookmark:", error);
905
966
  throw error;
906
967
  }
907
968
  },
908
- async pin(id, isPinned, userId) {
969
+ /**
970
+ * Pin or unpin a bookmark
971
+ * @param documentId - Bookmark's documentId
972
+ * @param isPinned - Pin status
973
+ * @param userId - Editor's documentId
974
+ * @returns Updated bookmark
975
+ */
976
+ async pin(documentId, isPinned, userId) {
909
977
  try {
910
- const bookmark = await strapi.entityService.update(
911
- "plugin::magic-mark.bookmark",
912
- id,
913
- {
914
- data: {
915
- isPinned,
916
- updatedBy: userId
917
- }
978
+ const bookmark = await strapi.documents(BOOKMARK_UID).update({
979
+ documentId,
980
+ data: {
981
+ isPinned,
982
+ updaterId: userId
918
983
  }
919
- );
984
+ });
920
985
  return bookmark;
921
986
  } catch (error) {
922
987
  strapi.log.error("[magic-mark] Error pinning bookmark:", error);
923
988
  throw error;
924
989
  }
925
990
  },
991
+ /**
992
+ * Reorder bookmarks
993
+ * @param bookmarkIds - Array of bookmark documentIds in new order
994
+ * @param userId - Editor's documentId
995
+ * @returns Updated bookmarks
996
+ */
926
997
  async reorder(bookmarkIds, userId) {
927
998
  try {
928
999
  const updates = bookmarkIds.map(
929
- (id, index2) => strapi.entityService.update(
930
- "plugin::magic-mark.bookmark",
931
- id,
932
- {
933
- data: {
934
- order: index2,
935
- updatedBy: userId
936
- }
1000
+ (documentId, index2) => strapi.documents(BOOKMARK_UID).update({
1001
+ documentId,
1002
+ data: {
1003
+ order: index2,
1004
+ updaterId: userId
937
1005
  }
938
- )
1006
+ })
939
1007
  );
940
1008
  return Promise.all(updates);
941
1009
  } catch (error) {
@@ -943,6 +1011,11 @@ const bookmarkService = ({ strapi }) => ({
943
1011
  throw error;
944
1012
  }
945
1013
  },
1014
+ /**
1015
+ * Validate URL format
1016
+ * @param url - URL to validate
1017
+ * @returns True if valid
1018
+ */
946
1019
  validateUrl(url) {
947
1020
  try {
948
1021
  new URL(url);
@@ -952,6 +1025,13 @@ const bookmarkService = ({ strapi }) => ({
952
1025
  }
953
1026
  }
954
1027
  });
1028
+ let pluginVersion = "2.0.0";
1029
+ try {
1030
+ const pkgPath = path.join(__dirname, "../../../package.json");
1031
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
1032
+ pluginVersion = pkg.version || "2.0.0";
1033
+ } catch (e) {
1034
+ }
955
1035
  const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
956
1036
  const licenseGuardService = ({ strapi }) => ({
957
1037
  /**
@@ -1017,7 +1097,8 @@ const licenseGuardService = ({ strapi }) => ({
1017
1097
  * Get user agent (server context)
1018
1098
  */
1019
1099
  getUserAgent() {
1020
- return `Strapi/${strapi.config.get("info.strapi") || "5.0.0"} Node/${process.version} ${os__default.default.platform()}/${os__default.default.release()}`;
1100
+ const strapiVersion = strapi.config.get("info.strapi") || "5.0.0";
1101
+ return `MagicMark/${pluginVersion} Strapi/${strapiVersion} Node/${process.version} ${os__default.default.platform()}/${os__default.default.release()}`;
1021
1102
  },
1022
1103
  /**
1023
1104
  * Create a license
@@ -1048,14 +1129,14 @@ const licenseGuardService = ({ strapi }) => ({
1048
1129
  });
1049
1130
  const data = await response.json();
1050
1131
  if (data.success) {
1051
- strapi.log.info(" License created successfully:", data.data.licenseKey);
1132
+ strapi.log.info("[SUCCESS] License created successfully:", data.data.licenseKey);
1052
1133
  return data.data;
1053
1134
  } else {
1054
- strapi.log.error(" License creation failed:", data);
1135
+ strapi.log.error("[ERROR] License creation failed:", data);
1055
1136
  return null;
1056
1137
  }
1057
1138
  } catch (error) {
1058
- strapi.log.error(" Error creating license:", error);
1139
+ strapi.log.error("[ERROR] Error creating license:", error);
1059
1140
  return null;
1060
1141
  }
1061
1142
  },
@@ -1084,7 +1165,7 @@ const licenseGuardService = ({ strapi }) => ({
1084
1165
  if (data.success) {
1085
1166
  const isValid = data.data.isActive && !data.data.isExpired;
1086
1167
  const statusInfo = data.data.isExpired ? "EXPIRED" : data.data.isActive ? "ACTIVE" : "INACTIVE";
1087
- strapi.log.info(`✅ License verified online: ${statusInfo} (Key: ${licenseKey?.substring(0, 8)}...)`);
1168
+ strapi.log.info(`[SUCCESS] License verified online: ${statusInfo} (Key: ${licenseKey?.substring(0, 8)}...)`);
1088
1169
  if (isValid) {
1089
1170
  const pluginStore = strapi.store({
1090
1171
  type: "plugin",
@@ -1097,16 +1178,16 @@ const licenseGuardService = ({ strapi }) => ({
1097
1178
  data: data.data
1098
1179
  };
1099
1180
  } else {
1100
- strapi.log.warn(`⚠️ License verification failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1181
+ strapi.log.warn(`[WARN] License verification failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1101
1182
  return { valid: false, data: null };
1102
1183
  }
1103
1184
  } catch (error) {
1104
1185
  if (allowGracePeriod) {
1105
- strapi.log.warn(`⚠️ Cannot verify license online: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
1106
- strapi.log.info(`🕐 Grace period active - accepting stored license key`);
1186
+ strapi.log.warn(`[WARN] Cannot verify license online: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
1187
+ strapi.log.info(`[GRACE] Grace period active - accepting stored license key`);
1107
1188
  return { valid: true, data: null, gracePeriod: true };
1108
1189
  }
1109
- strapi.log.error(`❌ Error verifying license: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
1190
+ strapi.log.error(`[ERROR] Error verifying license: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
1110
1191
  return { valid: false, data: null };
1111
1192
  }
1112
1193
  },
@@ -1129,10 +1210,10 @@ const licenseGuardService = ({ strapi }) => ({
1129
1210
  });
1130
1211
  const data = await response.json();
1131
1212
  if (data.success) {
1132
- strapi.log.debug(`📡 License ping successful: ${data.data?.isActive ? "ACTIVE" : "INACTIVE"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1213
+ strapi.log.debug(`[PING] License ping successful: ${data.data?.isActive ? "ACTIVE" : "INACTIVE"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1133
1214
  return data.data;
1134
1215
  } else {
1135
- strapi.log.debug(`⚠️ License ping failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1216
+ strapi.log.debug(`[WARN] License ping failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1136
1217
  return null;
1137
1218
  }
1138
1219
  } catch (error) {
@@ -1153,7 +1234,7 @@ const licenseGuardService = ({ strapi }) => ({
1153
1234
  }
1154
1235
  return null;
1155
1236
  } catch (error) {
1156
- strapi.log.error(" Error fetching license by key:", error);
1237
+ strapi.log.error("[ERROR] Error fetching license by key:", error);
1157
1238
  return null;
1158
1239
  }
1159
1240
  },
@@ -1170,7 +1251,7 @@ const licenseGuardService = ({ strapi }) => ({
1170
1251
  }
1171
1252
  return null;
1172
1253
  } catch (error) {
1173
- strapi.log.error(" Error fetching online stats:", error);
1254
+ strapi.log.error("[ERROR] Error fetching online stats:", error);
1174
1255
  return null;
1175
1256
  }
1176
1257
  },
@@ -1183,7 +1264,7 @@ const licenseGuardService = ({ strapi }) => ({
1183
1264
  const pingInterval = setInterval(async () => {
1184
1265
  await this.pingLicense(licenseKey);
1185
1266
  }, intervalMs);
1186
- strapi.log.info(`📡 Started pinging license every ${intervalMinutes} minutes`);
1267
+ strapi.log.info(`[PING] Started pinging license every ${intervalMinutes} minutes`);
1187
1268
  return pingInterval;
1188
1269
  },
1189
1270
  /**
@@ -1192,7 +1273,7 @@ const licenseGuardService = ({ strapi }) => ({
1192
1273
  */
1193
1274
  async initialize() {
1194
1275
  try {
1195
- strapi.log.info("🔐 Initializing License Guard...");
1276
+ strapi.log.info("[LICENSE] Initializing License Guard...");
1196
1277
  const pluginStore = strapi.store({
1197
1278
  type: "plugin",
1198
1279
  name: "magic-mark"
@@ -1208,29 +1289,29 @@ const licenseGuardService = ({ strapi }) => ({
1208
1289
  withinGracePeriod = hoursSinceValidation < gracePeriodHours;
1209
1290
  }
1210
1291
  strapi.log.info("──────────────────────────────────────────────────────────");
1211
- strapi.log.info(`📦 Plugin Store Check:`);
1292
+ strapi.log.info(`[STORE] Plugin Store Check:`);
1212
1293
  if (licenseKey) {
1213
- strapi.log.info(` License Key found: ${licenseKey}`);
1214
- strapi.log.info(` 🔑 Key (short): ${licenseKey.substring(0, 8)}...`);
1294
+ strapi.log.info(` [OK] License Key found: ${licenseKey}`);
1295
+ strapi.log.info(` [KEY] Key (short): ${licenseKey.substring(0, 8)}...`);
1215
1296
  if (lastValidated) {
1216
1297
  const lastValidatedDate = new Date(lastValidated);
1217
1298
  const hoursAgo = Math.floor((now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60));
1218
- strapi.log.info(` 🕐 Last validated: ${hoursAgo}h ago (Grace: ${withinGracePeriod ? "ACTIVE" : "EXPIRED"})`);
1299
+ strapi.log.info(` [TIME] Last validated: ${hoursAgo}h ago (Grace: ${withinGracePeriod ? "ACTIVE" : "EXPIRED"})`);
1219
1300
  } else {
1220
- strapi.log.info(` 🕐 Last validated: Never (Grace: ACTIVE for first ${gracePeriodHours}h)`);
1301
+ strapi.log.info(` [TIME] Last validated: Never (Grace: ACTIVE for first ${gracePeriodHours}h)`);
1221
1302
  }
1222
1303
  } else {
1223
- strapi.log.info(` No license key stored`);
1304
+ strapi.log.info(` [NONE] No license key stored`);
1224
1305
  }
1225
1306
  strapi.log.info("──────────────────────────────────────────────────────────");
1226
1307
  if (licenseKey) {
1227
- strapi.log.info("📄 Verifying stored license key...");
1308
+ strapi.log.info("[VERIFY] Verifying stored license key...");
1228
1309
  const verification = await this.verifyLicense(licenseKey, true);
1229
1310
  if (verification.valid) {
1230
1311
  if (verification.gracePeriod) {
1231
- strapi.log.info(" License accepted (offline mode / grace period)");
1312
+ strapi.log.info("[SUCCESS] License accepted (offline mode / grace period)");
1232
1313
  } else {
1233
- strapi.log.info(" License is valid and active");
1314
+ strapi.log.info("[SUCCESS] License is valid and active");
1234
1315
  }
1235
1316
  const pingInterval = this.startPinging(licenseKey, 15);
1236
1317
  strapi.licenseGuard = {
@@ -1240,17 +1321,17 @@ const licenseGuardService = ({ strapi }) => ({
1240
1321
  };
1241
1322
  return { valid: true, data: verification.data };
1242
1323
  } else {
1243
- strapi.log.warn("⚠️ Stored license is invalid or expired");
1324
+ strapi.log.warn("[WARN] Stored license is invalid or expired");
1244
1325
  if (!withinGracePeriod) {
1245
1326
  await pluginStore.delete({ key: "licenseKey" });
1246
1327
  await pluginStore.delete({ key: "lastValidated" });
1247
1328
  }
1248
1329
  }
1249
1330
  }
1250
- strapi.log.warn("⚠️ No valid license found. Plugin will run with limited functionality.");
1331
+ strapi.log.warn("[WARN] No valid license found. Plugin will run with limited functionality.");
1251
1332
  return { valid: false, demo: true };
1252
1333
  } catch (error) {
1253
- strapi.log.error(" Error initializing license guard:", error);
1334
+ strapi.log.error("[ERROR] Error initializing license guard:", error);
1254
1335
  return { valid: false, error: error.message };
1255
1336
  }
1256
1337
  },
@@ -1259,7 +1340,7 @@ const licenseGuardService = ({ strapi }) => ({
1259
1340
  */
1260
1341
  async storeLicenseKey(licenseKey) {
1261
1342
  try {
1262
- strapi.log.info(`🔐 Storing license key: ${licenseKey}`);
1343
+ strapi.log.info(`[STORE] Storing license key: ${licenseKey}`);
1263
1344
  const pluginStore = strapi.store({
1264
1345
  type: "plugin",
1265
1346
  name: "magic-mark"
@@ -1268,14 +1349,14 @@ const licenseGuardService = ({ strapi }) => ({
1268
1349
  await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
1269
1350
  const stored = await pluginStore.get({ key: "licenseKey" });
1270
1351
  if (stored === licenseKey) {
1271
- strapi.log.info(" License key stored and verified successfully");
1352
+ strapi.log.info("[SUCCESS] License key stored and verified successfully");
1272
1353
  return true;
1273
1354
  } else {
1274
- strapi.log.error(" License key storage verification failed");
1355
+ strapi.log.error("[ERROR] License key storage verification failed");
1275
1356
  return false;
1276
1357
  }
1277
1358
  } catch (error) {
1278
- strapi.log.error(" Error storing license key:", error);
1359
+ strapi.log.error("[ERROR] Error storing license key:", error);
1279
1360
  return false;
1280
1361
  }
1281
1362
  },
@@ -1286,7 +1367,7 @@ const licenseGuardService = ({ strapi }) => ({
1286
1367
  const licenseGuard = strapi.licenseGuard;
1287
1368
  if (licenseGuard && licenseGuard.pingInterval) {
1288
1369
  clearInterval(licenseGuard.pingInterval);
1289
- strapi.log.info("🛑 License pinging stopped");
1370
+ strapi.log.info("[STOP] License pinging stopped");
1290
1371
  }
1291
1372
  }
1292
1373
  });