data-primals-engine 1.2.6-rc3 → 1.3.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 (40) hide show
  1. package/README.md +38 -2
  2. package/client/package-lock.json +247 -4354
  3. package/client/package.json +16 -15
  4. package/client/src/App.jsx +13 -20
  5. package/client/src/App.scss +1 -0
  6. package/client/src/AssistantChat.jsx +5 -4
  7. package/client/src/DataEditor.jsx +2 -2
  8. package/client/src/DataTable.jsx +47 -3
  9. package/client/src/ExportDialog.jsx +2 -2
  10. package/client/src/Field.jsx +6 -18
  11. package/client/src/KanbanCard.jsx +4 -2
  12. package/client/src/KanbanConfigModal.jsx +5 -7
  13. package/client/src/ModelCreatorField.jsx +9 -9
  14. package/client/src/PackGallery.jsx +89 -9
  15. package/client/src/PackGallery.scss +58 -4
  16. package/client/src/RTETrans.jsx +11 -0
  17. package/client/src/RelationValue.jsx +3 -4
  18. package/client/src/constants.js +1 -1
  19. package/client/src/core/data.js +2 -1
  20. package/client/src/translations.js +80 -0
  21. package/package.json +8 -1
  22. package/server.js +4 -4
  23. package/src/constants.js +6 -0
  24. package/src/defaultModels.js +23 -10
  25. package/src/filter.js +35 -5
  26. package/src/i18n.js +1 -1
  27. package/src/modules/{assistant.js → assistant/assistant.js} +42 -27
  28. package/src/modules/assistant/constants.js +16 -0
  29. package/src/modules/data/data.core.js +1 -3
  30. package/src/modules/data/data.js +4601 -4525
  31. package/src/modules/data/data.routes.js +29 -3
  32. package/src/modules/mongodb.js +3 -1
  33. package/src/modules/user.js +12 -1
  34. package/src/modules/workflow.js +198 -117
  35. package/src/packs.js +1015 -9
  36. package/src/services/index.js +11 -0
  37. package/src/services/stripe.js +141 -0
  38. package/test/data.integration.test.js +66 -3
  39. package/test/workflow.actions.integration.test.js +474 -0
  40. package/test/workflow.integration.test.js +1 -1
@@ -0,0 +1,11 @@
1
+ import * as stripeService from './stripe.js';
2
+ // Importez d'autres services ici à l'avenir
3
+ // import * as anotherService from './anotherService.js';
4
+
5
+ /**
6
+ * Registre central de tous les services exposés au moteur de workflow.
7
+ */
8
+ export const services = {
9
+ stripe: stripeService,
10
+ // anotherService: anotherService,
11
+ };
@@ -0,0 +1,141 @@
1
+ import Stripe from 'stripe';
2
+ import {editData, insertData, searchData} from '../modules/data/index.js';
3
+ import {getEnv} from "../modules/user.js";
4
+
5
+
6
+ /**
7
+ * Crée ou récupère un client Stripe pour un utilisateur de notre système.
8
+ */
9
+ async function getOrCreateCustomer(user, db) {
10
+ // Vérifier que l'utilisateur et son email de contact sont valides
11
+ if (!user || !user.contact || !user.contact.email) {
12
+ // Dans un vrai scénario, il faudrait peut-être chercher l'email directement sur le modèle user
13
+ const userRecord = await db.findOne('user', {_id: user._id});
14
+ if(!userRecord || !userRecord.contact || !userRecord.contact.email) {
15
+ throw new Error("User or user contact email is missing.");
16
+ }
17
+ user = userRecord;
18
+ }
19
+
20
+ const existingCustomer = await db.findOne('StripeCustomer', { user: user._id });
21
+
22
+ if (existingCustomer) {
23
+ return existingCustomer;
24
+ }
25
+
26
+ // Créer le client sur Stripe
27
+ const stripeCustomer = await stripe.customers.create({
28
+ email: user.contact.email,
29
+ name: user.username,
30
+ metadata: {
31
+ userId: user._id.toString() // Lien vers notre système
32
+ }
33
+ });
34
+
35
+ // Enregistrer le client dans notre BDD
36
+ const { insertedIds } = await insertData('StripeCustomer', {
37
+ user: user._id,
38
+ stripeCustomerId: stripeCustomer.id,
39
+ email: user.email
40
+ }, {}, user);
41
+
42
+ const newCustomer = await searchData({ model: 'StripeCustomer', filter: { _id: insertedIds[0] } });
43
+ return newCustomer.data[0];
44
+ }
45
+ /**
46
+ * Crée une session de checkout Stripe.
47
+ */
48
+ export async function createCheckoutSession(priceId, user, db) {
49
+ const customer = await getOrCreateCustomer(user, db);
50
+ const session = await stripe.checkout.sessions.create({
51
+ payment_method_types: ['card'],
52
+ mode: 'subscription' // ou 'payment' pour un paiement unique
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Vérifie le statut d'une session de checkout.
58
+ */
59
+ export async function verifyCheckoutSession(sessionId) {
60
+ try {
61
+ const session = await stripe.checkout.sessions.retrieve(sessionId);
62
+ return {
63
+ status: session.status,
64
+ payment_status: session.payment_status,
65
+ customer: session.customer,
66
+ subscription: session.subscription
67
+ };
68
+ } catch (error) {
69
+ console.error("Error verifying checkout session:", error);
70
+ throw new Error("Could not verify checkout session.");
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Vérifie la signature d'un webhook Stripe pour s'assurer qu'il est authentique.
76
+ * C'est une étape de sécurité cruciale.
77
+ *
78
+ * @param {object} headers - Les en-têtes de la requête (notamment 'stripe-signature').
79
+ * @param {string} rawBody - Le corps brut de la requête.
80
+ * @returns {Stripe.Event} L'objet événement Stripe vérifié.
81
+ * @throws {Error} Si la signature est invalide ou si les en-têtes/body sont manquants.
82
+ */
83
+ export async function verifyWebhookSignature(headers, rawBody, user) {
84
+ // Initialisation de Stripe avec la clé secrète (à charger depuis une config sécurisée)
85
+ const env = await getEnv(user);
86
+ const stripe = env.STRIPE_SECRET_KEY ? new Stripe(env.STRIPE_SECRET_KEY) : null;
87
+ const webhookSecret = env.STRIPE_WEBHOOK_SECRET;
88
+
89
+ if( !stripe ){
90
+ throw new Error("Stripe unknown API key.");
91
+ }
92
+ if (!webhookSecret) {
93
+ throw new Error("Stripe webhook secret (STRIPE_WEBHOOK_SECRET) is not configured on the server.");
94
+ }
95
+
96
+ const signature = headers['stripe-signature'];
97
+ if (!signature) {
98
+ throw new Error("Missing 'stripe-signature' header.");
99
+ }
100
+
101
+ try {
102
+ return stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);
103
+ } catch (err) {
104
+ console.error(`❌ Error verifying Stripe webhook signature: ${err.message}`);
105
+ throw new Error(`Webhook signature verification failed: ${err.message}`);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Crée un remboursement pour une commande spécifique.
111
+ */
112
+ export async function createRefund(returnId, db, user) {
113
+ const returnRequest = await db.findOne('return', { _id: returnId });
114
+ if (!returnRequest) {
115
+ throw new Error("Return request not found.");
116
+ }
117
+
118
+ // Trouver la commande et le paiement associés
119
+ const order = await db.findOne('order', { _id: returnRequest.order });
120
+ if (!order || !order.paymentIntentId) {
121
+ throw new Error("Associated order or its paymentIntentId not found.");
122
+ }
123
+
124
+ const refund = await stripe.refunds.create({
125
+ payment_intent: order.paymentIntentId,
126
+ amount: Math.round(returnRequest.amount * 100), // Stripe attend le montant en centimes
127
+ reason: 'requested_by_customer',
128
+ metadata: {
129
+ returnId: returnId.toString(),
130
+ orderId: order.orderId
131
+ }
132
+ });
133
+
134
+ // Mettre à jour le statut de la demande de retour dans notre BDD
135
+ await editData('return', { _id: returnId }, {
136
+ status: 'refunded',
137
+ refundDate: new Date()
138
+ }, {}, user);
139
+
140
+ return { refundId: refund.id, status: refund.status };
141
+ }
@@ -697,7 +697,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
697
697
 
698
698
  // 2. Insérer le pack dans la collection pour le rendre disponible au test
699
699
  const packInsertResult = await testPacksColInstance.insertOne(mockPack);
700
- const packId = packInsertResult.insertedId;
700
+ const packId = packInsertResult.insertedId.toString();
701
701
 
702
702
  // 3. Appeler la fonction à tester avec la nouvelle signature
703
703
  const result = await installPack(packId, currentTestUser, 'en');
@@ -751,7 +751,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
751
751
  };
752
752
 
753
753
  const packInsertResult = await testPacksColInstance.insertOne(mockPackWithInvalidModel);
754
- const packId = packInsertResult.insertedId;
754
+ const packId = packInsertResult.insertedId.toString();
755
755
 
756
756
  const result = await installPack(packId, currentTestUser, 'en');
757
757
 
@@ -770,6 +770,69 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
770
770
  const validModel = await testModelsColInstance.findOne({ name: 'validPackModel', _user: currentTestUser.username });
771
771
  expect(validModel).not.toBeNull();
772
772
  });
773
+
774
+ it('devrait correctement insérer des données JSON depuis un pack', async () => {
775
+ const { currentTestUser } = await setupTestContext();
776
+ // 1. Définir le pack avec des données JSON
777
+ const mockPackWithJson = {
778
+ name: 'test-pack-with-json',
779
+ models: [
780
+ {
781
+ name: 'packConfig',
782
+ description: 'Modèle avec un champ JSON',
783
+ fields: [
784
+ { name: 'configName', type: 'string', required: true },
785
+ { name: 'settings', type: 'code', language: 'json' }
786
+ ]
787
+ }
788
+ ],
789
+ data: {
790
+ all: {
791
+ packConfig: [
792
+ {
793
+ configName: 'mainConfig',
794
+ settings: {
795
+ theme: 'dark',
796
+ notifications: {
797
+ enabled: true,
798
+ level: 'important'
799
+ }
800
+ }
801
+ }
802
+ ]
803
+ }
804
+ }
805
+ };
806
+
807
+ // 2. Insérer le pack
808
+ const packInsertResult = await testPacksColInstance.insertOne(mockPackWithJson);
809
+ const packId = packInsertResult.insertedId.toString();
810
+
811
+ // 3. Appeler la fonction à tester
812
+ const result = await installPack(packId, currentTestUser, 'en');
813
+
814
+ // 4. Assertions
815
+ expect(result.success, `L'installation du pack JSON a échoué: ${result.errors?.join('; ')}`).toBe(true);
816
+ expect(result.summary.models.installed).toHaveLength(1);
817
+ expect(result.summary.datas.inserted).toBe(1);
818
+
819
+ // 5. Vérifier les données insérées en BDD
820
+ const datasCol = await getCollectionForUser(currentTestUser);
821
+ const insertedConfig = await datasCol.findOne({ _model: 'packConfig', _user: currentTestUser.username });
822
+
823
+ expect(insertedConfig).not.toBeNull();
824
+ expect(insertedConfig.configName).toBe('mainConfig');
825
+ expect(typeof insertedConfig.settings).toBe('object');
826
+ expect(insertedConfig.settings).toEqual({
827
+ theme: 'dark',
828
+ notifications: {
829
+ enabled: true,
830
+ level: 'important'
831
+ }
832
+ });
833
+
834
+ await getCollection('packs').deleteMany({ _id: packId});
835
+ });
773
836
  });
774
837
  // In test/data.integration.test.js
775
838
 
@@ -795,7 +858,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
795
858
  name: 'produit',
796
859
  type: 'relation',
797
860
  relation: 'produitTestFiltre',
798
- relationFilter: { actif: true } // Only link active products
861
+ relationFilter: { "$eq":["$actif", true] } // Only link active products
799
862
  }
800
863
  ]
801
864
  };