strapi-plugin-magic-mark 1.1.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.
Files changed (68) hide show
  1. package/COPYRIGHT_NOTICE.txt +20 -0
  2. package/LICENSE +28 -0
  3. package/README.md +372 -0
  4. package/dist/_chunks/App-7ZH1Reka.mjs +1390 -0
  5. package/dist/_chunks/App-CMSut1pt.js +1392 -0
  6. package/dist/_chunks/de-Bag-366k.mjs +49 -0
  7. package/dist/_chunks/de-Dic_hhjg.js +49 -0
  8. package/dist/_chunks/en-C5BvHqNo.js +54 -0
  9. package/dist/_chunks/en-zokEerzt.mjs +54 -0
  10. package/dist/_chunks/es-BlSQpU1z.js +54 -0
  11. package/dist/_chunks/es-Br1ucP3h.mjs +54 -0
  12. package/dist/_chunks/fr-BHciYPYG.js +54 -0
  13. package/dist/_chunks/fr-Dzo3kt_q.mjs +54 -0
  14. package/dist/_chunks/index-B-Cc7QNW.mjs +322 -0
  15. package/dist/_chunks/index-B11QBtag.js +324 -0
  16. package/dist/_chunks/index-DYHEyGJr.mjs +2020 -0
  17. package/dist/_chunks/index-DkkmdRgb.js +2024 -0
  18. package/dist/_chunks/pt-DQoGyzyD.mjs +54 -0
  19. package/dist/_chunks/pt-Dawo5aUA.js +54 -0
  20. package/dist/admin/index.js +3 -0
  21. package/dist/admin/index.mjs +4 -0
  22. package/dist/admin/src/components/AdvancedFilterButton.d.ts +3 -0
  23. package/dist/admin/src/components/AdvancedFilterModal.d.ts +21 -0
  24. package/dist/admin/src/components/CreateEditModal.d.ts +11 -0
  25. package/dist/admin/src/components/CreateViewModal.d.ts +9 -0
  26. package/dist/admin/src/components/CustomSelect.d.ts +13 -0
  27. package/dist/admin/src/components/FilterPreview.d.ts +6 -0
  28. package/dist/admin/src/components/HomePage.d.ts +0 -0
  29. package/dist/admin/src/components/Initializer.d.ts +6 -0
  30. package/dist/admin/src/components/LicenseGuard.d.ts +6 -0
  31. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  32. package/dist/admin/src/components/QueryBuilder.d.ts +23 -0
  33. package/dist/admin/src/components/SimpleAdvancedFilterModal.d.ts +15 -0
  34. package/dist/admin/src/components/ViewsListPopover.d.ts +2 -0
  35. package/dist/admin/src/components/ViewsWidget.d.ts +8 -0
  36. package/dist/admin/src/index.d.ts +15 -0
  37. package/dist/admin/src/pages/App.d.ts +3 -0
  38. package/dist/admin/src/pages/HomePage.d.ts +3 -0
  39. package/dist/admin/src/pages/HomePageModern.d.ts +3 -0
  40. package/dist/admin/src/pages/License/index.d.ts +3 -0
  41. package/dist/admin/src/permissions.d.ts +15 -0
  42. package/dist/admin/src/pluginId.d.ts +2 -0
  43. package/dist/admin/src/utils/queryGenerator.d.ts +28 -0
  44. package/dist/admin/src/utils/queryParser.d.ts +25 -0
  45. package/dist/admin/src/utils/queryToStructure.d.ts +20 -0
  46. package/dist/server/index.js +1309 -0
  47. package/dist/server/index.mjs +1307 -0
  48. package/dist/server/src/bootstrap.d.ts +5 -0
  49. package/dist/server/src/config/index.d.ts +5 -0
  50. package/dist/server/src/content-types/index.d.ts +82 -0
  51. package/dist/server/src/controllers/controller.d.ts +11 -0
  52. package/dist/server/src/controllers/index.d.ts +20 -0
  53. package/dist/server/src/controllers/license.d.ts +27 -0
  54. package/dist/server/src/destroy.d.ts +5 -0
  55. package/dist/server/src/index.d.ts +195 -0
  56. package/dist/server/src/middlewares/index.d.ts +4 -0
  57. package/dist/server/src/middlewares/license-check.d.ts +6 -0
  58. package/dist/server/src/policies/index.d.ts +4 -0
  59. package/dist/server/src/policies/license-check.d.ts +6 -0
  60. package/dist/server/src/register.d.ts +5 -0
  61. package/dist/server/src/routes/admin.d.ts +12 -0
  62. package/dist/server/src/routes/content-api.d.ts +5 -0
  63. package/dist/server/src/routes/index.d.ts +18 -0
  64. package/dist/server/src/services/index.d.ts +57 -0
  65. package/dist/server/src/services/license-guard.d.ts +103 -0
  66. package/dist/server/src/services/service.d.ts +33 -0
  67. package/package.json +108 -0
  68. package/strapi-server.js +4 -0
@@ -0,0 +1,1309 @@
1
+ "use strict";
2
+ const crypto = require("crypto");
3
+ const os = require("os");
4
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
5
+ const crypto__default = /* @__PURE__ */ _interopDefault(crypto);
6
+ const os__default = /* @__PURE__ */ _interopDefault(os);
7
+ const magicMarkActions = {
8
+ actions: [
9
+ {
10
+ section: "plugins",
11
+ displayName: "Read",
12
+ uid: "settings.read",
13
+ subCategory: "Settings",
14
+ pluginName: "magic-mark"
15
+ },
16
+ {
17
+ section: "plugins",
18
+ displayName: "Edit",
19
+ uid: "settings.update",
20
+ subCategory: "Settings",
21
+ pluginName: "magic-mark"
22
+ }
23
+ ]
24
+ };
25
+ const register = async ({ strapi }) => {
26
+ await strapi.admin.services.permission.actionProvider.registerMany(
27
+ magicMarkActions.actions
28
+ );
29
+ strapi.log.info("[Magic-Mark] Plugin registered successfully");
30
+ };
31
+ const bootstrap = ({ strapi }) => {
32
+ strapi.log.info("[Magic-Mark] Plugin bootstrapping...");
33
+ try {
34
+ const licenseGuardService2 = strapi.plugin("magic-mark").service("license-guard");
35
+ setTimeout(async () => {
36
+ const licenseStatus = await licenseGuardService2.initialize();
37
+ if (!licenseStatus.valid) {
38
+ strapi.log.error("╔════════════════════════════════════════════════════════════════╗");
39
+ strapi.log.error("║ ❌ MAGICMARK PLUGIN - NO VALID LICENSE ║");
40
+ strapi.log.error("║ ║");
41
+ strapi.log.error("║ This plugin requires a valid license to operate. ║");
42
+ strapi.log.error("║ Please activate your license via Admin UI: ║");
43
+ strapi.log.error("║ Go to Settings → MagicMark → License ║");
44
+ strapi.log.error("║ ║");
45
+ strapi.log.error("║ The plugin will run with limited functionality until ║");
46
+ strapi.log.error("║ a valid license is activated. ║");
47
+ strapi.log.error("╚════════════════════════════════════════════════════════════════╝");
48
+ } else if (licenseStatus.valid) {
49
+ const pluginStore = strapi.store({
50
+ type: "plugin",
51
+ name: "magic-mark"
52
+ });
53
+ const storedKey = await pluginStore.get({ key: "licenseKey" });
54
+ strapi.log.info("╔════════════════════════════════════════════════════════════════╗");
55
+ strapi.log.info("║ ✅ MAGICMARK PLUGIN LICENSE ACTIVE ║");
56
+ strapi.log.info("║ ║");
57
+ if (licenseStatus.data) {
58
+ strapi.log.info(`║ License: ${licenseStatus.data.licenseKey} ║`);
59
+ strapi.log.info(`║ User: ${licenseStatus.data.firstName} ${licenseStatus.data.lastName}`.padEnd(66) + "║");
60
+ strapi.log.info(`║ Email: ${licenseStatus.data.email}`.padEnd(66) + "║");
61
+ } else if (storedKey) {
62
+ strapi.log.info(`║ License: ${storedKey} (Offline Mode) ║`);
63
+ strapi.log.info(`║ Status: Grace Period Active ║`);
64
+ }
65
+ strapi.log.info("║ ║");
66
+ strapi.log.info("║ 🔄 Auto-pinging every 15 minutes ║");
67
+ strapi.log.info("╚════════════════════════════════════════════════════════════════╝");
68
+ }
69
+ }, 3e3);
70
+ } catch (error) {
71
+ strapi.log.error("❌ Error initializing License Guard:", error);
72
+ }
73
+ strapi.log.info("[Magic-Mark] Plugin bootstrapped successfully");
74
+ };
75
+ const destroy = ({ strapi }) => {
76
+ try {
77
+ const licenseGuardService2 = strapi.plugin("magic-mark")?.service("license-guard");
78
+ if (licenseGuardService2) {
79
+ licenseGuardService2.cleanup();
80
+ strapi.log.info("✅ License Guard cleanup completed");
81
+ }
82
+ } catch (error) {
83
+ strapi.log.error("❌ Error during License Guard cleanup:", error);
84
+ }
85
+ };
86
+ const config = {
87
+ default: {},
88
+ validator: () => {
89
+ }
90
+ };
91
+ const contentTypes = {
92
+ bookmark: {
93
+ schema: {
94
+ kind: "collectionType",
95
+ collectionName: "magic_bookmarks",
96
+ info: {
97
+ singularName: "bookmark",
98
+ pluralName: "bookmarks",
99
+ displayName: "Magic Bookmark",
100
+ description: "Saved filter views for quick access with path and query parameters"
101
+ },
102
+ options: {
103
+ draftAndPublish: false,
104
+ comment: "Magic Bookmarks - Saved Views with Filters"
105
+ },
106
+ pluginOptions: {
107
+ "content-manager": { visible: false },
108
+ "content-type-builder": { visible: false }
109
+ },
110
+ attributes: {
111
+ name: {
112
+ type: "string",
113
+ required: true,
114
+ maxLength: 100,
115
+ configurable: false
116
+ },
117
+ // Path: /content-manager/collection-types/api::article.article
118
+ path: {
119
+ type: "string",
120
+ required: true,
121
+ configurable: false,
122
+ description: "Content Manager path (e.g., /content-manager/collection-types/api::article.article)"
123
+ },
124
+ // Query: filters[status][$eq]=published&sort=createdAt:DESC
125
+ query: {
126
+ type: "text",
127
+ configurable: false,
128
+ description: "URL query parameters (e.g., filters[status][$eq]=published&sort=createdAt:DESC)"
129
+ },
130
+ emoji: {
131
+ type: "string",
132
+ default: "🔖",
133
+ configurable: false,
134
+ maxLength: 2
135
+ },
136
+ isPinned: {
137
+ type: "boolean",
138
+ default: false,
139
+ configurable: false
140
+ },
141
+ order: {
142
+ type: "integer",
143
+ default: 0,
144
+ configurable: false
145
+ },
146
+ description: {
147
+ type: "text",
148
+ configurable: false
149
+ },
150
+ // Array of role IDs that have access to this bookmark
151
+ sharedWithRoles: {
152
+ type: "json",
153
+ configurable: false,
154
+ description: "Array of role IDs that have access to this bookmark"
155
+ },
156
+ // Array of user IDs that have direct access to this bookmark
157
+ sharedWithUsers: {
158
+ type: "json",
159
+ configurable: false,
160
+ description: "Array of user IDs that have direct access to this bookmark"
161
+ },
162
+ // Whether this bookmark is public to all admin users
163
+ isPublic: {
164
+ type: "boolean",
165
+ default: false,
166
+ configurable: false,
167
+ description: "If true, all admin users can see this bookmark"
168
+ }
169
+ }
170
+ }
171
+ }
172
+ };
173
+ const bookmarkController = ({ strapi }) => ({
174
+ async getAll(ctx) {
175
+ try {
176
+ const user = ctx.state.user;
177
+ if (!user) {
178
+ return ctx.throw(401, "User not authenticated");
179
+ }
180
+ const bookmarks = await strapi.plugin("magic-mark").service("bookmarks").findAll(user.id);
181
+ ctx.body = {
182
+ data: bookmarks,
183
+ meta: {
184
+ count: bookmarks.length
185
+ }
186
+ };
187
+ } catch (error) {
188
+ ctx.throw(500, `Error fetching bookmarks: ${error}`);
189
+ }
190
+ },
191
+ async create(ctx) {
192
+ try {
193
+ const user = ctx.state.user;
194
+ if (!user) {
195
+ return ctx.throw(401, "User not authenticated");
196
+ }
197
+ const { name, path, query, emoji, description, sharedWithRoles, sharedWithUsers, isPublic } = ctx.request.body;
198
+ if (!name || name.trim().length === 0) {
199
+ return ctx.throw(400, "Name is required");
200
+ }
201
+ if (!path || path.trim().length === 0) {
202
+ return ctx.throw(400, "Path is required");
203
+ }
204
+ const service = strapi.plugin("magic-mark").service("bookmarks");
205
+ const bookmark = await service.create(name, path, query, emoji, description, user.id, sharedWithRoles, sharedWithUsers, isPublic);
206
+ ctx.body = {
207
+ data: bookmark
208
+ };
209
+ } catch (error) {
210
+ ctx.throw(500, `Error creating bookmark: ${error}`);
211
+ }
212
+ },
213
+ async update(ctx) {
214
+ try {
215
+ const user = ctx.state.user;
216
+ if (!user) {
217
+ return ctx.throw(401, "User not authenticated");
218
+ }
219
+ const { id } = ctx.params;
220
+ const { name, path, query, emoji, description, isPinned, order, sharedWithRoles, sharedWithUsers, isPublic } = ctx.request.body;
221
+ if (!name || name.trim().length === 0) {
222
+ return ctx.throw(400, "Name is required");
223
+ }
224
+ if (!path || path.trim().length === 0) {
225
+ return ctx.throw(400, "Path is required");
226
+ }
227
+ const bookmark = await strapi.plugin("magic-mark").service("bookmarks").update(id, { name, path, query, emoji, description, isPinned, order, sharedWithRoles, sharedWithUsers, isPublic }, user.id);
228
+ ctx.body = {
229
+ data: bookmark
230
+ };
231
+ } catch (error) {
232
+ ctx.throw(500, `Error updating bookmark: ${error}`);
233
+ }
234
+ },
235
+ async delete(ctx) {
236
+ try {
237
+ const user = ctx.state.user;
238
+ if (!user) {
239
+ return ctx.throw(401, "User not authenticated");
240
+ }
241
+ const { id } = ctx.params;
242
+ await strapi.plugin("magic-mark").service("bookmarks").delete(id);
243
+ ctx.body = {
244
+ data: { success: true }
245
+ };
246
+ } catch (error) {
247
+ ctx.throw(500, `Error deleting bookmark: ${error}`);
248
+ }
249
+ },
250
+ async pin(ctx) {
251
+ try {
252
+ const user = ctx.state.user;
253
+ if (!user) {
254
+ return ctx.throw(401, "User not authenticated");
255
+ }
256
+ const { id } = ctx.params;
257
+ const { isPinned } = ctx.request.body;
258
+ const bookmark = await strapi.plugin("magic-mark").service("bookmarks").pin(id, isPinned, user.id);
259
+ ctx.body = {
260
+ data: bookmark
261
+ };
262
+ } catch (error) {
263
+ ctx.throw(500, `Error pinning bookmark: ${error}`);
264
+ }
265
+ },
266
+ async reorder(ctx) {
267
+ try {
268
+ const user = ctx.state.user;
269
+ if (!user) {
270
+ return ctx.throw(401, "User not authenticated");
271
+ }
272
+ const { bookmarkIds } = ctx.request.body;
273
+ if (!bookmarkIds || !Array.isArray(bookmarkIds)) {
274
+ return ctx.throw(400, "Bookmark IDs array is required");
275
+ }
276
+ const bookmarks = await strapi.plugin("magic-mark").service("bookmarks").reorder(bookmarkIds, user.id);
277
+ ctx.body = {
278
+ data: bookmarks
279
+ };
280
+ } catch (error) {
281
+ ctx.throw(500, `Error reordering bookmarks: ${error}`);
282
+ }
283
+ },
284
+ async getRoles(ctx) {
285
+ try {
286
+ const user = ctx.state.user;
287
+ if (!user) {
288
+ return ctx.throw(401, "User not authenticated");
289
+ }
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
+ );
300
+ const rolesWithDetails = roles?.map((role) => ({
301
+ id: role.id,
302
+ name: role.name,
303
+ code: role.code,
304
+ description: role.description,
305
+ userCount: role.users?.length || 0,
306
+ isCustom: !["super_admin", "editor", "author"].includes(role.code)
307
+ // Mark custom roles
308
+ })) || [];
309
+ ctx.body = {
310
+ data: {
311
+ data: rolesWithDetails
312
+ }
313
+ };
314
+ } catch (error) {
315
+ ctx.throw(500, `Error fetching roles: ${error}`);
316
+ }
317
+ },
318
+ async getUsers(ctx) {
319
+ try {
320
+ const user = ctx.state.user;
321
+ if (!user) {
322
+ return ctx.throw(401, "User not authenticated");
323
+ }
324
+ let users = [];
325
+ try {
326
+ const adminUserService = strapi.admin?.services?.user || strapi.service("admin::user");
327
+ if (adminUserService?.findPage) {
328
+ const results = await adminUserService.findPage({
329
+ pagination: {
330
+ page: 1,
331
+ pageSize: 100
332
+ }
333
+ });
334
+ users = results.results || [];
335
+ } else if (adminUserService?.find) {
336
+ users = await adminUserService.find();
337
+ }
338
+ } catch (err) {
339
+ console.log("[Magic-Mark] Admin service error, trying entity service:", err);
340
+ }
341
+ if (!users || users.length === 0) {
342
+ try {
343
+ users = await strapi.entityService.findMany("admin::user", {
344
+ limit: 100
345
+ });
346
+ } catch (err) {
347
+ console.log("[Magic-Mark] Entity service error:", err);
348
+ }
349
+ }
350
+ if (!users || users.length === 0) {
351
+ try {
352
+ users = await strapi.db.query("admin::user").findMany({
353
+ limit: 100
354
+ });
355
+ } catch (err) {
356
+ console.log("[Magic-Mark] DB query error:", err);
357
+ }
358
+ }
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
+ })));
366
+ ctx.body = {
367
+ data: {
368
+ data: filteredUsers.map((u) => ({
369
+ id: u.id,
370
+ firstname: u.firstname || "",
371
+ lastname: u.lastname || "",
372
+ email: u.email,
373
+ username: u.username || u.email
374
+ }))
375
+ }
376
+ };
377
+ } catch (error) {
378
+ ctx.throw(500, `Error fetching users: ${error}`);
379
+ }
380
+ }
381
+ });
382
+ const license = ({ strapi }) => ({
383
+ /**
384
+ * Auto-create license with logged-in admin user data
385
+ */
386
+ async autoCreate(ctx) {
387
+ try {
388
+ const adminUser = ctx.state.user;
389
+ if (!adminUser) {
390
+ return ctx.unauthorized("No admin user logged in");
391
+ }
392
+ const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
393
+ const license2 = await licenseGuard.createLicense({
394
+ email: adminUser.email,
395
+ firstName: adminUser.firstname || "Admin",
396
+ lastName: adminUser.lastname || "User"
397
+ });
398
+ if (!license2) {
399
+ return ctx.badRequest("Failed to create license");
400
+ }
401
+ await licenseGuard.storeLicenseKey(license2.licenseKey);
402
+ const pingInterval = licenseGuard.startPinging(license2.licenseKey, 15);
403
+ strapi.licenseGuard = {
404
+ licenseKey: license2.licenseKey,
405
+ pingInterval,
406
+ data: license2
407
+ };
408
+ return ctx.send({
409
+ success: true,
410
+ message: "License automatically created and activated",
411
+ data: license2
412
+ });
413
+ } catch (error) {
414
+ strapi.log.error("Error auto-creating license:", error);
415
+ return ctx.badRequest("Error creating license");
416
+ }
417
+ },
418
+ /**
419
+ * Get current license status
420
+ */
421
+ async getStatus(ctx) {
422
+ try {
423
+ const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
424
+ const pluginStore = strapi.store({
425
+ type: "plugin",
426
+ name: "magic-mark"
427
+ });
428
+ const licenseKey = await pluginStore.get({ key: "licenseKey" });
429
+ if (!licenseKey) {
430
+ return ctx.send({
431
+ success: false,
432
+ demo: true,
433
+ valid: false,
434
+ message: "No license found. Running in demo mode."
435
+ });
436
+ }
437
+ const verification = await licenseGuard.verifyLicense(licenseKey);
438
+ const license2 = await licenseGuard.getLicenseByKey(licenseKey);
439
+ return ctx.send({
440
+ success: true,
441
+ valid: verification.valid,
442
+ demo: false,
443
+ data: {
444
+ licenseKey,
445
+ email: license2?.email || null,
446
+ firstName: license2?.firstName || null,
447
+ lastName: license2?.lastName || null,
448
+ isActive: license2?.isActive || false,
449
+ isExpired: license2?.isExpired || false,
450
+ isOnline: license2?.isOnline || false,
451
+ expiresAt: license2?.expiresAt,
452
+ lastPingAt: license2?.lastPingAt,
453
+ deviceName: license2?.deviceName,
454
+ deviceId: license2?.deviceId,
455
+ ipAddress: license2?.ipAddress,
456
+ features: {
457
+ premium: license2?.featurePremium || false,
458
+ advanced: license2?.featureAdvanced || false,
459
+ enterprise: license2?.featureEnterprise || false,
460
+ custom: license2?.featureCustom || false
461
+ },
462
+ maxDevices: license2?.maxDevices || 1,
463
+ currentDevices: license2?.currentDevices || 0
464
+ }
465
+ });
466
+ } catch (error) {
467
+ strapi.log.error("Error getting license status:", error);
468
+ return ctx.badRequest("Error getting license status");
469
+ }
470
+ },
471
+ /**
472
+ * Create and activate a new license
473
+ */
474
+ async createAndActivate(ctx) {
475
+ try {
476
+ const { email, firstName, lastName } = ctx.request.body;
477
+ if (!email || !firstName || !lastName) {
478
+ return ctx.badRequest("Email, firstName, and lastName are required");
479
+ }
480
+ const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
481
+ const license2 = await licenseGuard.createLicense({ email, firstName, lastName });
482
+ if (!license2) {
483
+ return ctx.badRequest("Failed to create license");
484
+ }
485
+ await licenseGuard.storeLicenseKey(license2.licenseKey);
486
+ const pingInterval = licenseGuard.startPinging(license2.licenseKey, 15);
487
+ strapi.licenseGuard = {
488
+ licenseKey: license2.licenseKey,
489
+ pingInterval,
490
+ data: license2
491
+ };
492
+ return ctx.send({
493
+ success: true,
494
+ message: "License created and activated successfully",
495
+ data: license2
496
+ });
497
+ } catch (error) {
498
+ strapi.log.error("Error creating license:", error);
499
+ return ctx.badRequest("Error creating license");
500
+ }
501
+ },
502
+ /**
503
+ * Manually ping the current license
504
+ */
505
+ async ping(ctx) {
506
+ try {
507
+ const pluginStore = strapi.store({
508
+ type: "plugin",
509
+ name: "magic-mark"
510
+ });
511
+ const licenseKey = await pluginStore.get({ key: "licenseKey" });
512
+ if (!licenseKey) {
513
+ return ctx.badRequest("No license key found");
514
+ }
515
+ const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
516
+ const pingResult = await licenseGuard.pingLicense(licenseKey);
517
+ if (!pingResult) {
518
+ return ctx.badRequest("Ping failed");
519
+ }
520
+ return ctx.send({
521
+ success: true,
522
+ message: "License pinged successfully",
523
+ data: pingResult
524
+ });
525
+ } catch (error) {
526
+ strapi.log.error("Error pinging license:", error);
527
+ return ctx.badRequest("Error pinging license");
528
+ }
529
+ },
530
+ /**
531
+ * Store and validate an existing license key
532
+ */
533
+ async storeKey(ctx) {
534
+ try {
535
+ const { licenseKey, email } = ctx.request.body;
536
+ if (!licenseKey || !licenseKey.trim()) {
537
+ return ctx.badRequest("License key is required");
538
+ }
539
+ if (!email || !email.trim()) {
540
+ return ctx.badRequest("Email address is required");
541
+ }
542
+ const trimmedKey = licenseKey.trim();
543
+ const trimmedEmail = email.trim().toLowerCase();
544
+ const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
545
+ const verification = await licenseGuard.verifyLicense(trimmedKey);
546
+ if (!verification.valid) {
547
+ strapi.log.warn(`⚠️ Invalid license key attempted: ${trimmedKey.substring(0, 8)}...`);
548
+ return ctx.badRequest("Invalid or expired license key");
549
+ }
550
+ const license2 = await licenseGuard.getLicenseByKey(trimmedKey);
551
+ if (!license2) {
552
+ strapi.log.warn(`⚠️ License not found in database: ${trimmedKey.substring(0, 8)}...`);
553
+ return ctx.badRequest("License not found");
554
+ }
555
+ if (license2.email.toLowerCase() !== trimmedEmail) {
556
+ strapi.log.warn(`⚠️ Email mismatch for license key: ${trimmedKey.substring(0, 8)}... (Attempted: ${trimmedEmail})`);
557
+ return ctx.badRequest("Email address does not match this license key");
558
+ }
559
+ await licenseGuard.storeLicenseKey(trimmedKey);
560
+ const pingInterval = licenseGuard.startPinging(trimmedKey, 15);
561
+ strapi.licenseGuard = {
562
+ licenseKey: trimmedKey,
563
+ pingInterval,
564
+ data: verification.data
565
+ };
566
+ strapi.log.info(`✅ Existing license key validated and stored: ${trimmedKey.substring(0, 8)}... (Email: ${trimmedEmail})`);
567
+ return ctx.send({
568
+ success: true,
569
+ message: "License key validated and activated successfully",
570
+ data: verification.data
571
+ });
572
+ } catch (error) {
573
+ strapi.log.error("Error storing license key:", error);
574
+ return ctx.badRequest("Error validating license key");
575
+ }
576
+ }
577
+ });
578
+ const controllers = {
579
+ bookmarks: bookmarkController,
580
+ license
581
+ };
582
+ const admin = {
583
+ type: "admin",
584
+ routes: [
585
+ // Get available roles for sharing
586
+ {
587
+ method: "GET",
588
+ path: "/roles",
589
+ handler: "bookmarks.getRoles",
590
+ config: {
591
+ policies: []
592
+ }
593
+ },
594
+ // Get available users for sharing
595
+ {
596
+ method: "GET",
597
+ path: "/users",
598
+ handler: "bookmarks.getUsers",
599
+ config: {
600
+ policies: []
601
+ }
602
+ },
603
+ // Get all bookmarks
604
+ {
605
+ method: "GET",
606
+ path: "/bookmarks",
607
+ handler: "bookmarks.getAll",
608
+ config: {
609
+ policies: []
610
+ }
611
+ },
612
+ // Reorder bookmarks (SPECIFIC - must come before :id routes)
613
+ {
614
+ method: "POST",
615
+ path: "/bookmarks/reorder",
616
+ handler: "bookmarks.reorder",
617
+ config: {
618
+ policies: []
619
+ }
620
+ },
621
+ // Pin/Unpin bookmark (SPECIFIC - must come before generic :id)
622
+ {
623
+ method: "POST",
624
+ path: "/bookmarks/:id/pin",
625
+ handler: "bookmarks.pin",
626
+ config: {
627
+ policies: []
628
+ }
629
+ },
630
+ // Create bookmark
631
+ {
632
+ method: "POST",
633
+ path: "/bookmarks",
634
+ handler: "bookmarks.create",
635
+ config: {
636
+ policies: []
637
+ }
638
+ },
639
+ // Update bookmark
640
+ {
641
+ method: "PUT",
642
+ path: "/bookmarks/:id",
643
+ handler: "bookmarks.update",
644
+ config: {
645
+ policies: []
646
+ }
647
+ },
648
+ // Delete bookmark
649
+ {
650
+ method: "DELETE",
651
+ path: "/bookmarks/:id",
652
+ handler: "bookmarks.delete",
653
+ config: {
654
+ policies: []
655
+ }
656
+ },
657
+ // License Management
658
+ {
659
+ method: "GET",
660
+ path: "/license/status",
661
+ handler: "license.getStatus",
662
+ config: {
663
+ policies: []
664
+ }
665
+ },
666
+ {
667
+ method: "POST",
668
+ path: "/license/auto-create",
669
+ handler: "license.autoCreate",
670
+ config: {
671
+ policies: []
672
+ }
673
+ },
674
+ {
675
+ method: "POST",
676
+ path: "/license/create",
677
+ handler: "license.createAndActivate",
678
+ config: {
679
+ policies: []
680
+ }
681
+ },
682
+ {
683
+ method: "POST",
684
+ path: "/license/ping",
685
+ handler: "license.ping",
686
+ config: {
687
+ policies: []
688
+ }
689
+ },
690
+ {
691
+ method: "POST",
692
+ path: "/license/store-key",
693
+ handler: "license.storeKey",
694
+ config: {
695
+ policies: []
696
+ }
697
+ }
698
+ ]
699
+ };
700
+ const contentApi = {
701
+ type: "content-api",
702
+ routes: []
703
+ };
704
+ const routes = {
705
+ admin,
706
+ "content-api": contentApi
707
+ };
708
+ const licenseCheck$1 = (config2, { strapi }) => {
709
+ return async (ctx, next) => {
710
+ if (ctx.request.url.includes("/magic-mark/license/")) {
711
+ return await next();
712
+ }
713
+ if (ctx.request.url.includes("/admin/login")) {
714
+ return await next();
715
+ }
716
+ try {
717
+ const licenseGuard = strapi.plugin("magic-mark")?.service("license-guard");
718
+ if (!licenseGuard) {
719
+ return await next();
720
+ }
721
+ const pluginStore = strapi.store({
722
+ type: "plugin",
723
+ name: "magic-mark"
724
+ });
725
+ const licenseKey = await pluginStore.get({ key: "licenseKey" });
726
+ if (!licenseKey) {
727
+ strapi.log.warn("⚠️ MagicMark: No license key found");
728
+ return await next();
729
+ }
730
+ const verification = await licenseGuard.verifyLicense(licenseKey, true);
731
+ if (!verification.valid && !verification.gracePeriod) {
732
+ strapi.log.warn("⚠️ MagicMark: Invalid license detected");
733
+ }
734
+ return await next();
735
+ } catch (error) {
736
+ strapi.log.error("Error in license check middleware:", error);
737
+ return await next();
738
+ }
739
+ };
740
+ };
741
+ const middlewares = {
742
+ "license-check": licenseCheck$1
743
+ };
744
+ const licenseCheck = async (policyContext, config2, { strapi }) => {
745
+ try {
746
+ const licenseGuard = strapi.plugin("magic-mark").service("license-guard");
747
+ const pluginStore = strapi.store({ type: "plugin", name: "magic-mark" });
748
+ const licenseKey = await pluginStore.get({ key: "licenseKey" });
749
+ if (!licenseKey) {
750
+ strapi.log.warn("⚠️ API access denied: No license key found");
751
+ return policyContext.unauthorized("No license found. Please activate the plugin first.");
752
+ }
753
+ const verification = await licenseGuard.verifyLicense(licenseKey);
754
+ if (!verification.valid) {
755
+ strapi.log.warn("⚠️ API access denied: Invalid license");
756
+ return policyContext.unauthorized("Invalid or expired license. Please check your license status.");
757
+ }
758
+ const license2 = await licenseGuard.getLicenseByKey(licenseKey);
759
+ if (!license2) {
760
+ strapi.log.warn("⚠️ API access denied: License not found in database");
761
+ return policyContext.unauthorized("License not found. Please contact support.");
762
+ }
763
+ if (!license2.isActive) {
764
+ strapi.log.warn("⚠️ API access denied: License is inactive");
765
+ return policyContext.unauthorized("License is inactive. Please activate your license.");
766
+ }
767
+ if (license2.isExpired) {
768
+ strapi.log.warn("⚠️ API access denied: License has expired");
769
+ return policyContext.unauthorized("License has expired. Please renew your license.");
770
+ }
771
+ return true;
772
+ } catch (error) {
773
+ strapi.log.error("Error checking license:", error);
774
+ return policyContext.unauthorized("Error verifying license. Please try again.");
775
+ }
776
+ };
777
+ const policies = {
778
+ "license-check": licenseCheck
779
+ };
780
+ const bookmarkService = ({ strapi }) => ({
781
+ async findAll(userId) {
782
+ 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
+ );
801
+ const accessibleBookmarks = allBookmarks?.filter((bookmark) => {
802
+ if (bookmark.createdBy?.id === userId) {
803
+ return true;
804
+ }
805
+ if (bookmark.isPublic) {
806
+ return true;
807
+ }
808
+ if (bookmark.sharedWithUsers && Array.isArray(bookmark.sharedWithUsers)) {
809
+ if (bookmark.sharedWithUsers.includes(userId)) {
810
+ return true;
811
+ }
812
+ }
813
+ if (bookmark.sharedWithRoles && Array.isArray(bookmark.sharedWithRoles)) {
814
+ return bookmark.sharedWithRoles.some(
815
+ (roleId) => userRoleIds.includes(roleId)
816
+ );
817
+ }
818
+ return false;
819
+ }) || [];
820
+ return accessibleBookmarks;
821
+ } catch (error) {
822
+ strapi.log.error("[magic-mark] Error finding bookmarks:", error);
823
+ throw error;
824
+ }
825
+ },
826
+ async create(name, path, query, emoji, description, userId, sharedWithRoles, sharedWithUsers, isPublic) {
827
+ try {
828
+ const bookmarks = await strapi.entityService.findMany(
829
+ "plugin::magic-mark.bookmark",
830
+ {
831
+ sort: [{ order: "desc" }],
832
+ limit: 1
833
+ }
834
+ );
835
+ 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
+ }
852
+ }
853
+ );
854
+ return bookmark;
855
+ } catch (error) {
856
+ strapi.log.error("[magic-mark] Error creating bookmark:", error);
857
+ throw error;
858
+ }
859
+ },
860
+ async update(id, data, userId) {
861
+ 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) {
870
+ throw new Error("Unauthorized: You can only edit your own bookmarks");
871
+ }
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
+ }
889
+ }
890
+ );
891
+ return bookmark;
892
+ } catch (error) {
893
+ strapi.log.error("[magic-mark] Error updating bookmark:", error);
894
+ throw error;
895
+ }
896
+ },
897
+ async delete(id) {
898
+ try {
899
+ return await strapi.entityService.delete(
900
+ "plugin::magic-mark.bookmark",
901
+ id
902
+ );
903
+ } catch (error) {
904
+ strapi.log.error("[magic-mark] Error deleting bookmark:", error);
905
+ throw error;
906
+ }
907
+ },
908
+ async pin(id, isPinned, userId) {
909
+ try {
910
+ const bookmark = await strapi.entityService.update(
911
+ "plugin::magic-mark.bookmark",
912
+ id,
913
+ {
914
+ data: {
915
+ isPinned,
916
+ updatedBy: userId
917
+ }
918
+ }
919
+ );
920
+ return bookmark;
921
+ } catch (error) {
922
+ strapi.log.error("[magic-mark] Error pinning bookmark:", error);
923
+ throw error;
924
+ }
925
+ },
926
+ async reorder(bookmarkIds, userId) {
927
+ try {
928
+ 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
+ }
937
+ }
938
+ )
939
+ );
940
+ return Promise.all(updates);
941
+ } catch (error) {
942
+ strapi.log.error("[magic-mark] Error reordering bookmarks:", error);
943
+ throw error;
944
+ }
945
+ },
946
+ validateUrl(url) {
947
+ try {
948
+ new URL(url);
949
+ return true;
950
+ } catch {
951
+ return false;
952
+ }
953
+ }
954
+ });
955
+ const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
956
+ const licenseGuardService = ({ strapi }) => ({
957
+ /**
958
+ * Get license server URL (hardcoded and immutable for security)
959
+ * @returns The fixed license server URL - cannot be overridden
960
+ */
961
+ getLicenseServerUrl() {
962
+ return LICENSE_SERVER_URL;
963
+ },
964
+ /**
965
+ * Generate a unique device ID based on machine identifiers
966
+ */
967
+ generateDeviceId() {
968
+ try {
969
+ const networkInterfaces = os__default.default.networkInterfaces();
970
+ const macAddresses = [];
971
+ Object.values(networkInterfaces).forEach((interfaces) => {
972
+ interfaces?.forEach((iface) => {
973
+ if (iface.mac && iface.mac !== "00:00:00:00:00:00") {
974
+ macAddresses.push(iface.mac);
975
+ }
976
+ });
977
+ });
978
+ const identifier = `${macAddresses.join("-")}-${os__default.default.hostname()}`;
979
+ return crypto__default.default.createHash("sha256").update(identifier).digest("hex").substring(0, 32);
980
+ } catch (error) {
981
+ strapi.log.error("Error generating device ID:", error);
982
+ return crypto__default.default.randomBytes(16).toString("hex");
983
+ }
984
+ },
985
+ /**
986
+ * Get device name
987
+ */
988
+ getDeviceName() {
989
+ try {
990
+ return os__default.default.hostname() || "Unknown Device";
991
+ } catch (error) {
992
+ return "Unknown Device";
993
+ }
994
+ },
995
+ /**
996
+ * Get server IP address
997
+ */
998
+ getIpAddress() {
999
+ try {
1000
+ const networkInterfaces = os__default.default.networkInterfaces();
1001
+ for (const name of Object.keys(networkInterfaces)) {
1002
+ const interfaces = networkInterfaces[name];
1003
+ if (interfaces) {
1004
+ for (const iface of interfaces) {
1005
+ if (iface.family === "IPv4" && !iface.internal) {
1006
+ return iface.address;
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ return "127.0.0.1";
1012
+ } catch (error) {
1013
+ return "127.0.0.1";
1014
+ }
1015
+ },
1016
+ /**
1017
+ * Get user agent (server context)
1018
+ */
1019
+ getUserAgent() {
1020
+ return `Strapi/${strapi.config.get("info.strapi") || "5.0.0"} Node/${process.version} ${os__default.default.platform()}/${os__default.default.release()}`;
1021
+ },
1022
+ /**
1023
+ * Create a license
1024
+ */
1025
+ async createLicense({ email, firstName, lastName }) {
1026
+ try {
1027
+ const deviceId = this.generateDeviceId();
1028
+ const deviceName = this.getDeviceName();
1029
+ const ipAddress = this.getIpAddress();
1030
+ const userAgent = this.getUserAgent();
1031
+ const licenseServerUrl = this.getLicenseServerUrl();
1032
+ const response = await fetch(`${licenseServerUrl}/api/licenses/create`, {
1033
+ method: "POST",
1034
+ headers: {
1035
+ "Content-Type": "application/json"
1036
+ },
1037
+ body: JSON.stringify({
1038
+ email,
1039
+ firstName,
1040
+ lastName,
1041
+ deviceName,
1042
+ deviceId,
1043
+ ipAddress,
1044
+ userAgent,
1045
+ pluginName: "magic-mark",
1046
+ productName: "MagicMark - Advanced Query Builder"
1047
+ })
1048
+ });
1049
+ const data = await response.json();
1050
+ if (data.success) {
1051
+ strapi.log.info("✅ License created successfully:", data.data.licenseKey);
1052
+ return data.data;
1053
+ } else {
1054
+ strapi.log.error("❌ License creation failed:", data);
1055
+ return null;
1056
+ }
1057
+ } catch (error) {
1058
+ strapi.log.error("❌ Error creating license:", error);
1059
+ return null;
1060
+ }
1061
+ },
1062
+ /**
1063
+ * Verify a license (with grace period support)
1064
+ */
1065
+ async verifyLicense(licenseKey, allowGracePeriod = false) {
1066
+ try {
1067
+ const controller = new AbortController();
1068
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
1069
+ const licenseServerUrl = this.getLicenseServerUrl();
1070
+ const response = await fetch(`${licenseServerUrl}/api/licenses/verify`, {
1071
+ method: "POST",
1072
+ headers: {
1073
+ "Content-Type": "application/json"
1074
+ },
1075
+ body: JSON.stringify({
1076
+ licenseKey,
1077
+ pluginName: "magic-mark",
1078
+ productName: "MagicMark - Advanced Query Builder"
1079
+ }),
1080
+ signal: controller.signal
1081
+ });
1082
+ clearTimeout(timeoutId);
1083
+ const data = await response.json();
1084
+ if (data.success) {
1085
+ const isValid = data.data.isActive && !data.data.isExpired;
1086
+ const statusInfo = data.data.isExpired ? "EXPIRED" : data.data.isActive ? "ACTIVE" : "INACTIVE";
1087
+ strapi.log.info(`✅ License verified online: ${statusInfo} (Key: ${licenseKey?.substring(0, 8)}...)`);
1088
+ if (isValid) {
1089
+ const pluginStore = strapi.store({
1090
+ type: "plugin",
1091
+ name: "magic-mark"
1092
+ });
1093
+ await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
1094
+ }
1095
+ return {
1096
+ valid: isValid,
1097
+ data: data.data
1098
+ };
1099
+ } else {
1100
+ strapi.log.warn(`⚠️ License verification failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1101
+ return { valid: false, data: null };
1102
+ }
1103
+ } catch (error) {
1104
+ 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`);
1107
+ return { valid: true, data: null, gracePeriod: true };
1108
+ }
1109
+ strapi.log.error(`❌ Error verifying license: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
1110
+ return { valid: false, data: null };
1111
+ }
1112
+ },
1113
+ /**
1114
+ * Ping a license (lightweight check)
1115
+ */
1116
+ async pingLicense(licenseKey) {
1117
+ try {
1118
+ const licenseServerUrl = this.getLicenseServerUrl();
1119
+ const response = await fetch(`${licenseServerUrl}/api/licenses/ping`, {
1120
+ method: "POST",
1121
+ headers: {
1122
+ "Content-Type": "application/json"
1123
+ },
1124
+ body: JSON.stringify({
1125
+ licenseKey,
1126
+ pluginName: "magic-mark",
1127
+ productName: "MagicMark - Advanced Query Builder"
1128
+ })
1129
+ });
1130
+ const data = await response.json();
1131
+ if (data.success) {
1132
+ strapi.log.debug(`📡 License ping successful: ${data.data?.isActive ? "ACTIVE" : "INACTIVE"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1133
+ return data.data;
1134
+ } else {
1135
+ strapi.log.debug(`⚠️ License ping failed: ${data.message || "Unknown error"} (Key: ${licenseKey?.substring(0, 8)}...)`);
1136
+ return null;
1137
+ }
1138
+ } catch (error) {
1139
+ strapi.log.debug(`License ping error: ${error.message} (Key: ${licenseKey?.substring(0, 8)}...)`);
1140
+ return null;
1141
+ }
1142
+ },
1143
+ /**
1144
+ * Get license by key
1145
+ */
1146
+ async getLicenseByKey(licenseKey) {
1147
+ try {
1148
+ const licenseServerUrl = this.getLicenseServerUrl();
1149
+ const response = await fetch(`${licenseServerUrl}/api/licenses/key/${licenseKey}`);
1150
+ const data = await response.json();
1151
+ if (data.success) {
1152
+ return data.data;
1153
+ }
1154
+ return null;
1155
+ } catch (error) {
1156
+ strapi.log.error("❌ Error fetching license by key:", error);
1157
+ return null;
1158
+ }
1159
+ },
1160
+ /**
1161
+ * Get online statistics
1162
+ */
1163
+ async getOnlineStats() {
1164
+ try {
1165
+ const licenseServerUrl = this.getLicenseServerUrl();
1166
+ const response = await fetch(`${licenseServerUrl}/api/licenses/stats/online`);
1167
+ const data = await response.json();
1168
+ if (data.success) {
1169
+ return data.data;
1170
+ }
1171
+ return null;
1172
+ } catch (error) {
1173
+ strapi.log.error("❌ Error fetching online stats:", error);
1174
+ return null;
1175
+ }
1176
+ },
1177
+ /**
1178
+ * Start periodic pinging for a license
1179
+ */
1180
+ startPinging(licenseKey, intervalMinutes = 15) {
1181
+ const intervalMs = intervalMinutes * 60 * 1e3;
1182
+ this.pingLicense(licenseKey);
1183
+ const pingInterval = setInterval(async () => {
1184
+ await this.pingLicense(licenseKey);
1185
+ }, intervalMs);
1186
+ strapi.log.info(`📡 Started pinging license every ${intervalMinutes} minutes`);
1187
+ return pingInterval;
1188
+ },
1189
+ /**
1190
+ * Initialize license guard
1191
+ * Checks for existing license or prompts for creation
1192
+ */
1193
+ async initialize() {
1194
+ try {
1195
+ strapi.log.info("🔐 Initializing License Guard...");
1196
+ const pluginStore = strapi.store({
1197
+ type: "plugin",
1198
+ name: "magic-mark"
1199
+ });
1200
+ const licenseKey = await pluginStore.get({ key: "licenseKey" });
1201
+ const lastValidated = await pluginStore.get({ key: "lastValidated" });
1202
+ const now = /* @__PURE__ */ new Date();
1203
+ const gracePeriodHours = 24;
1204
+ let withinGracePeriod = false;
1205
+ if (lastValidated) {
1206
+ const lastValidatedDate = new Date(lastValidated);
1207
+ const hoursSinceValidation = (now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60);
1208
+ withinGracePeriod = hoursSinceValidation < gracePeriodHours;
1209
+ }
1210
+ strapi.log.info("──────────────────────────────────────────────────────────");
1211
+ strapi.log.info(`📦 Plugin Store Check:`);
1212
+ if (licenseKey) {
1213
+ strapi.log.info(` ✅ License Key found: ${licenseKey}`);
1214
+ strapi.log.info(` 🔑 Key (short): ${licenseKey.substring(0, 8)}...`);
1215
+ if (lastValidated) {
1216
+ const lastValidatedDate = new Date(lastValidated);
1217
+ const hoursAgo = Math.floor((now.getTime() - lastValidatedDate.getTime()) / (1e3 * 60 * 60));
1218
+ strapi.log.info(` 🕐 Last validated: ${hoursAgo}h ago (Grace: ${withinGracePeriod ? "ACTIVE" : "EXPIRED"})`);
1219
+ } else {
1220
+ strapi.log.info(` 🕐 Last validated: Never (Grace: ACTIVE for first ${gracePeriodHours}h)`);
1221
+ }
1222
+ } else {
1223
+ strapi.log.info(` ❌ No license key stored`);
1224
+ }
1225
+ strapi.log.info("──────────────────────────────────────────────────────────");
1226
+ if (licenseKey) {
1227
+ strapi.log.info("📄 Verifying stored license key...");
1228
+ const verification = await this.verifyLicense(licenseKey, true);
1229
+ if (verification.valid) {
1230
+ if (verification.gracePeriod) {
1231
+ strapi.log.info("✅ License accepted (offline mode / grace period)");
1232
+ } else {
1233
+ strapi.log.info("✅ License is valid and active");
1234
+ }
1235
+ const pingInterval = this.startPinging(licenseKey, 15);
1236
+ strapi.licenseGuard = {
1237
+ licenseKey,
1238
+ pingInterval,
1239
+ data: verification.data
1240
+ };
1241
+ return { valid: true, data: verification.data };
1242
+ } else {
1243
+ strapi.log.warn("⚠️ Stored license is invalid or expired");
1244
+ if (!withinGracePeriod) {
1245
+ await pluginStore.delete({ key: "licenseKey" });
1246
+ await pluginStore.delete({ key: "lastValidated" });
1247
+ }
1248
+ }
1249
+ }
1250
+ strapi.log.warn("⚠️ No valid license found. Plugin will run with limited functionality.");
1251
+ return { valid: false, demo: true };
1252
+ } catch (error) {
1253
+ strapi.log.error("❌ Error initializing license guard:", error);
1254
+ return { valid: false, error: error.message };
1255
+ }
1256
+ },
1257
+ /**
1258
+ * Store license key after creation
1259
+ */
1260
+ async storeLicenseKey(licenseKey) {
1261
+ try {
1262
+ strapi.log.info(`🔐 Storing license key: ${licenseKey}`);
1263
+ const pluginStore = strapi.store({
1264
+ type: "plugin",
1265
+ name: "magic-mark"
1266
+ });
1267
+ await pluginStore.set({ key: "licenseKey", value: licenseKey });
1268
+ await pluginStore.set({ key: "lastValidated", value: (/* @__PURE__ */ new Date()).toISOString() });
1269
+ const stored = await pluginStore.get({ key: "licenseKey" });
1270
+ if (stored === licenseKey) {
1271
+ strapi.log.info("✅ License key stored and verified successfully");
1272
+ return true;
1273
+ } else {
1274
+ strapi.log.error("❌ License key storage verification failed");
1275
+ return false;
1276
+ }
1277
+ } catch (error) {
1278
+ strapi.log.error("❌ Error storing license key:", error);
1279
+ return false;
1280
+ }
1281
+ },
1282
+ /**
1283
+ * Cleanup on plugin destroy
1284
+ */
1285
+ cleanup() {
1286
+ const licenseGuard = strapi.licenseGuard;
1287
+ if (licenseGuard && licenseGuard.pingInterval) {
1288
+ clearInterval(licenseGuard.pingInterval);
1289
+ strapi.log.info("🛑 License pinging stopped");
1290
+ }
1291
+ }
1292
+ });
1293
+ const services = {
1294
+ bookmarks: bookmarkService,
1295
+ "license-guard": licenseGuardService
1296
+ };
1297
+ const index = {
1298
+ register,
1299
+ bootstrap,
1300
+ destroy,
1301
+ config,
1302
+ controllers,
1303
+ routes,
1304
+ services,
1305
+ contentTypes,
1306
+ policies,
1307
+ middlewares
1308
+ };
1309
+ module.exports = index;