strapi-plugin-magic-mark 2.0.0 → 3.0.1

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