From cccb8522c5287ff4d426eae8a4cd4f742c67f45a Mon Sep 17 00:00:00 2001
From: ofacklam <oliver.facklam@gmail.com>
Date: Sun, 23 Jun 2019 23:27:04 +0200
Subject: [PATCH] [User resolvers] finish resolvers

closes #53
move dislike query
rename user_like_group table to user_dislike_group  -> closes #66
bugfixes in messagemodel
---
 ...190511130512_create_user_dislike_group.js} |   8 +-
 db/seeds/08_dummy_dislikes.js                 |  22 ++
 db/seeds/09_dummy_questions.js                |  33 +++
 src/graphql/models/groupModel.ts              |   4 +-
 src/graphql/models/messageModel.ts            |  91 ++++----
 src/graphql/models/userModel.ts               |  61 ++++-
 src/graphql/object_resolvers/users.ts         |  29 ++-
 src/graphql/resolvers.ts                      |   9 +
 src/graphql/typeDefs/actions.graphql          |   1 +
 src/graphql/typeDefs/objects.graphql          |   1 -
 test/resolvers/data/onplatal/users.ts         | 208 ++++++++++++++++++
 11 files changed, 401 insertions(+), 66 deletions(-)
 rename db/migrations/{20190511130512_create_user_like_group.js => 20190511130512_create_user_dislike_group.js} (66%)
 create mode 100644 db/seeds/08_dummy_dislikes.js
 create mode 100644 db/seeds/09_dummy_questions.js

diff --git a/db/migrations/20190511130512_create_user_like_group.js b/db/migrations/20190511130512_create_user_dislike_group.js
similarity index 66%
rename from db/migrations/20190511130512_create_user_like_group.js
rename to db/migrations/20190511130512_create_user_dislike_group.js
index d390e81..1058e76 100644
--- a/db/migrations/20190511130512_create_user_like_group.js
+++ b/db/migrations/20190511130512_create_user_dislike_group.js
@@ -1,6 +1,6 @@
 
 exports.up = function (knex, Promise) {
-    return knex.schema.createTable('user_like_group', function (table) {
+    return knex.schema.createTable('user_dislike_group', function (table) {
         table.increments('id'); //autoincrementing (non-nullable) primary key
 
         table.string('uid').notNullable()
@@ -10,13 +10,9 @@ exports.up = function (knex, Promise) {
         table.string('gid').notNullable()
             .references('gid').inTable('groups')
             .onDelete('CASCADE');
-
-        table.unique(['uid', 'gid']);
-
-        table.boolean('isDislike').notNullable();
     });
 };
 
 exports.down = function (knex, Promise) {
-    return knex.schema.dropTable('user_like_group');
+    return knex.schema.dropTable('user_dislike_group');
 };
diff --git a/db/seeds/08_dummy_dislikes.js b/db/seeds/08_dummy_dislikes.js
new file mode 100644
index 0000000..c9bc2d2
--- /dev/null
+++ b/db/seeds/08_dummy_dislikes.js
@@ -0,0 +1,22 @@
+
+exports.seed = async function (knex, Promise) {
+    // Deletes ALL existing entries
+    await knex('user_dislike_group').del();
+    // Inserts seed entries
+    const users = [{
+        uid: 'hadrien.renaud',
+        gid: 'jtx'
+    }, {
+        uid: 'timothee.darcet',
+        gid: 'bobar'
+    }, {
+        uid: 'oliver.facklam',
+        gid: 'politix'
+    }
+
+    ];
+
+    await knex('user_dislike_group').insert(users);
+    console.log("finished running 08_dummy_dislikes");
+
+};
diff --git a/db/seeds/09_dummy_questions.js b/db/seeds/09_dummy_questions.js
new file mode 100644
index 0000000..ac366cb
--- /dev/null
+++ b/db/seeds/09_dummy_questions.js
@@ -0,0 +1,33 @@
+
+exports.seed = async function (knex, Promise) {
+    // Deletes ALL existing entries
+    // done by deleting all messages
+
+    // Inserts seed entries
+    const questions = [{
+        title: "Au secours !",
+        content: "Quand sont les perms BR ? J'arrive pas a me connecter a eduroam :'(",
+        author: "oliver.facklam",
+        recipient: "br"
+    },
+    {
+        title: "Prépass ?",
+        content: "Quand commence la prépasse du binet ?",
+        author: "timothee.darcet",
+        recipient : "subaisse"
+    },
+    ];
+
+    for (let q of questions) {
+        let mid = (await knex('messages').insert({ type: 'question' }, ['mid']))[0].mid;
+        await knex('messages_questions').insert({
+            mid: mid,
+            title: q.title,
+            content: q.content,
+            author: q.author,
+            recipient: q.recipient
+        });
+    }
+
+    console.log("finished running 09_dummy_questions");
+};
diff --git a/src/graphql/models/groupModel.ts b/src/graphql/models/groupModel.ts
index 476a073..d6f5248 100644
--- a/src/graphql/models/groupModel.ts
+++ b/src/graphql/models/groupModel.ts
@@ -9,7 +9,7 @@ import { SimpleGroup } from "../object_resolvers/groups/simpleGroups";
 import { MetaGroup } from "../object_resolvers/groups/metaGroups";
 import { Request } from "../object_resolvers/requests/requests";
 import { User } from "../object_resolvers/users";
-import knex from "../../../db/knex_router"
+import knex from "../../../db/knex_router";
 import { GroupCollection, GroupSet, Tools } from "../../utils/tools";
 import { createSubgroupArgs, editGroupArgs } from "../typeDefs/queries";
 import {Cache} from "../../utils/cache";
@@ -58,6 +58,7 @@ export class GroupModel {
      * @function getSimpleGroup
      * @summary Fonction qui renvoie un groupe simple donné.
      * @arg {string} gid - Identifiant demandé.
+     * @arg {boolean} load - Si load vaut false, les données ne seront pas chargées
      * @return {Promise(SimpleGroup)} Renvoie le groupe dont l'identifiant est 'gid'
      * @async
      * @rights connectedOrOnplatal
@@ -71,6 +72,7 @@ export class GroupModel {
      * @function getMetaGroup
      * @summary Fonction qui renvoie un méta-groupe donné.
      * @arg {string} gid - Identifiant demandé.
+     * @arg {boolean} load - Si load vaut false, les données ne seront pas chargées
      * @return {Promise(MetaGroup)} Renvoie le groupe dont l'identifiant est 'gid'
      * @async
      * @rights connectedOrOnplatal
diff --git a/src/graphql/models/messageModel.ts b/src/graphql/models/messageModel.ts
index e0cc950..d23531d 100644
--- a/src/graphql/models/messageModel.ts
+++ b/src/graphql/models/messageModel.ts
@@ -86,20 +86,6 @@ export class MessageModel {
         return Question.tryCreate(mid);
     }
 
-    /**
-     * @memberof GraphQL.Group#
-     * @function questions
-     * @summary Renvoie les questions adressées a ce groupe
-     * @arg {string} gid
-     * @return {Promise(Question[])}
-     * @rights viewer
-     * @async
-     */
-    async getAllQuestionsReceived(gid: string): Promise<Question[]> {
-        let result:Question[] = await knex('messages_questions').select().where('recipient', gid );
-        return result;
-    }
-
     /**
      * @memberof GraphQL.MessageModel#
      * @function getAnswer
@@ -113,21 +99,6 @@ export class MessageModel {
         return Answer.tryCreate(mid);
     }
 
-    /**
-     * @memberof GraphQL.Group#
-     * @function answers
-     * @summary Renvoie les réponses de ce groupe
-     * @arg {string} gid
-     * @return {Promise(Answer[])}
-     * @rights viewer
-     * @async
-     */
-    async getAllAnswersSent(gid: string): Promise<Answer[]> {
-        // let received_messages = await selectors.recievedMessages(user, groupUID);
-        let result:Answer[] = await knex('messages_answers').select().where('recipient', gid);
-        return result;
-    }
-
     /**
      * @memberof GraphQL.MessageModel#
      * @function getAllMessages
@@ -175,7 +146,7 @@ export class MessageModel {
     async getAllAnnouncementsSent(groups: GroupSet): Promise<Announcement[]> {
         let result = await knex.select('mid').from('announcements_authors').whereIn('gid', Array.from(groups));
         
-        return result.map(async obj => await this.getAnnouncement(obj.mid));
+        return Promise.all(result.map(obj => this.getAnnouncement(obj.mid)));
     }
 
     /**
@@ -190,7 +161,7 @@ export class MessageModel {
     async getAllAnnouncementsReceived(groups: GroupSet): Promise<Announcement[]> {
         let result = await knex.select('mid').from('announcements_recipients').whereIn('gid', Array.from(groups));
         
-        return result.map(async obj => await this.getAnnouncement(obj.mid));
+        return Promise.all(result.map(obj => this.getAnnouncement(obj.mid)));
     }
 
     /**
@@ -251,7 +222,7 @@ export class MessageModel {
     async getAllEventsFrom(groups: GroupSet): Promise<Event[]> {
         let result = await knex.select('mid').from('events_authors').whereIn('gid', Array.from(groups));
         
-        return result.map(async obj => await this.getEvent(obj.mid));
+        return Promise.all(result.map(obj => this.getEvent(obj.mid)));
     }
 
     /**
@@ -266,7 +237,7 @@ export class MessageModel {
     async getAllEventsTo(groups: GroupSet): Promise<Event[]> {
         let result = await knex.select('mid').from('events_recipients').whereIn('gid', Array.from(groups));
         
-        return result.map(async obj => await this.getEvent(obj.mid));
+        return Promise.all(result.map(obj => this.getEvent(obj.mid)));
     }
 
     /**
@@ -339,7 +310,7 @@ export class MessageModel {
     async getAllPrivatePosts(groups: GroupSet): Promise<PrivatePost[]> {
         let result = await knex.select('mid').from('messages_privateposts').whereIn('recipient', Array.from(groups));
         
-        return result.map(async obj => await this.getPrivatePost(obj.mid));
+        return Promise.all(result.map(obj => this.getPrivatePost(obj.mid)));
     }
 
     /**
@@ -347,23 +318,50 @@ export class MessageModel {
      * @function getAllQuestions
      * @summary Fonction qui renvoie toutes les questions posées aux groupes.
      * @arg {GroupSet} groups - Un ensemble d'identifiants, supposés valides.
-     * @return {Promise(Announcement[])} Renvoie toutes les annonces émises par ces groupes
+     * @return {Promise(Announcement[])} Renvoie toutes les questions posées a ces groupes
      * @async
      * @rights member of groups
      */
     async getAllQuestions(groups: GroupSet): Promise<Announcement[]> {
         let result = await knex.select('mid').from('messages_questions').whereIn('recipient', Array.from(groups));
         
-        return result.map(async obj => await this.getQuestion(obj.mid));
+        return Promise.all(result.map(obj => this.getQuestion(obj.mid)));
+    }
+
+    /**
+     * @memberof GraphQL.Group#
+     * @function getAllQuestionsReceived
+     * @summary Renvoie les questions adressées a ce groupe
+     * @arg {string} gid
+     * @return {Promise(Question[])}
+     * @rights viewer
+     * @async
+     */
+    async getAllQuestionsReceived(gid: string): Promise<Question[]> {
+        let result: Question[] = await knex('messages_questions').select().where('recipient', gid);
+        return result;
+    }
+
+    /**
+     * @memberof GraphQL.Group#
+     * @function getQuestionsFromUser
+     * @summary Renvoie les questions posées par le user
+     * @arg {string} uid
+     * @return {Promise(Question[])}
+     * @rights viewer
+     * @async
+     */
+    async getQuestionsFromUser(uid: string): Promise<Question[]> {
+        let result = await knex('messages_questions').select('mid').where('author', uid);
+        return Promise.all(result.map(obj => this.getQuestion(obj.mid)));
     }
-    
     
     /**
      * @memberof GraphQL.MessageModel#
      * @function getAllAnswers
      * @summary Fonction qui renvoie toutes les réponses données par le groupe.
      * @arg {GroupSet} groups - Un ensemble d'identifiants, supposés valides.
-     * @return {Promise(Announcement[])} Renvoie toutes les annonces émises par ces groupes
+     * @return {Promise(Announcement[])} Renvoie toutes les réponses données par ces groupes
      * @async
      * @rights member of groups
      */
@@ -374,10 +372,23 @@ export class MessageModel {
             .innerJoin('messages_questions', 'messages_answers.for_question', 'messages_questions.mid')
             .whereIn('messages_questions.recipient', groups);
         
-        return result.map(async obj => await this.getAnswer(obj.mid));
+        return Promise.all(result.map(obj => this.getAnswer(obj.mid)));
     }
 
-
+    /**
+     * @memberof GraphQL.Group#
+     * @function getAllAnswersSent
+     * @summary Renvoie les réponses de ce groupe
+     * @arg {string} gid
+     * @return {Promise(Answer[])}
+     * @rights viewer
+     * @async
+     */
+    async getAllAnswersSent(gid: string): Promise<Answer[]> {
+        // let received_messages = await selectors.recievedMessages(user, groupUID);
+        let result: Answer[] = await knex('messages_answers').select().where('recipient', gid);
+        return result;
+    }
 
     /**
      * @memberof GraphQL.MessageModel#
diff --git a/src/graphql/models/userModel.ts b/src/graphql/models/userModel.ts
index 777a5be..036c7e6 100644
--- a/src/graphql/models/userModel.ts
+++ b/src/graphql/models/userModel.ts
@@ -5,10 +5,14 @@
  */
 
 import { User } from "../object_resolvers/users";
-import { User as UT, userData } from "../../ldap/export/user"
+import { User as UT, userData } from "../../ldap/export/user";
+import { Group as GT, groupData } from "../../ldap/export/group";
+
+import knex from "../../../db/knex_router";
 import { searchTOLArgs, editProfileArgs } from "../typeDefs/queries";
 import { ApolloError } from "apollo-server-core";
 import {Cache} from "../../utils/cache";
+import { GroupCollection, Tools, GroupSet } from "../../utils/tools";
 
 export class UserModel {
 
@@ -33,7 +37,7 @@ export class UserModel {
      * @function getUser
      * @summary Fonction qui renvoie l'utilisateur demandé.
      * @arg {string} uid - Identifiant demandé.
-     * @arg {any} hints - Parametres supplémentaires.
+     * @arg {boolean} load - Si load vaut false, les données ne seront pas chargées.
      * @return {Promise(User)} Renvoie l'utilisateur dont l'identifiant est 'uid'
      * @async
      * @rights connectedOrOnplatal
@@ -42,6 +46,59 @@ export class UserModel {
         return this.cache.get(uid, (id) => User.tryCreate(id, load));
     }
 
+    /**
+     * @memberof GraphQL.UserModel#
+     * @function getMemberOf
+     * @summary Fonction qui renvoie les groupes stricts du user.
+     * @arg {string} uid - Identifiant demandé.
+     * @return {Promise(GroupCollection)} Renvoie les identifiants des groupes dont ce user est membre strict.
+     * @async
+     * @rights connectedOrOnplatal
+     */
+    async getMemberOf(uid: string): Promise<GroupCollection> {
+        let gd = new groupData();
+        gd.members = [uid];
+        let data = await GT.search(gd);
+
+        let simple = new GroupSet(data);
+        let meta = await Tools.metaGroupsOfGroups(simple);
+
+        return {simpleGroups: simple, metaGroups: meta};
+    }
+
+    /**
+     * @memberof GraphQL.UserModel#
+     * @function getAdminOf
+     * @summary Fonction qui renvoie les groupes dont le user est admin strict.
+     * @arg {string} uid - Identifiant demandé.
+     * @return {Promise(GroupCollection)} Renvoie les identifiants des groupes dont ce user est admin strict.
+     * @async
+     * @rights connectedOrOnplatal
+     */
+    async getAdminOf(uid: string): Promise<GroupCollection> {
+        let gd = new groupData();
+        gd.admins = [uid];
+        let data = await GT.search(gd);
+
+        let simple = new GroupSet(data);
+        let meta = await Tools.metaGroupsOfGroups(simple);
+
+        return { simpleGroups: simple, metaGroups: meta };
+    }
+
+    /**
+     * @memberof GraphQL.UserModel#
+     * @function getDislikes
+     * @summary Fonction qui renvoie les groupes que le contextUser veut cacher.
+     * @return {Promise(GroupSet)} Renvoie les identifiants des groupes que le contextUser veut cacher.
+     * @async
+     * @rights connected
+     */
+    async getDislikes(): Promise<GroupSet> {
+        let data = await knex.select('gid').from('user_dislike_group').where('uid', this.contextUser);
+        return new GroupSet(data.map(obj => obj.gid));
+    }
+
     /**
      * @memberof GraphQL.UserModel#
      * @function searchTOL
diff --git a/src/graphql/object_resolvers/users.ts b/src/graphql/object_resolvers/users.ts
index e19600c..a033770 100644
--- a/src/graphql/object_resolvers/users.ts
+++ b/src/graphql/object_resolvers/users.ts
@@ -286,7 +286,8 @@ export class User {
      * @async
      */
     async memberOf(args, context: Context, info): Promise<Group[]> {
-        throw "Not implemented";
+        let groups = await context.models.user.getMemberOf(this.uid);
+        return context.models.group.getAllGroupsByCollection(groups);
     }
 
     /**
@@ -340,7 +341,8 @@ export class User {
      * @async
      */
     async adminOf(args, context: Context, info): Promise<Group[]> {
-        throw "Not implemented";
+        let groups = await context.models.user.getAdminOf(this.uid);
+        return context.models.group.getAllGroupsByCollection(groups);
     }
 
     /**
@@ -385,18 +387,6 @@ export class User {
         return {simpleGroups: simple, metaGroups: meta};
     }
 
-    /**
-     * @memberof GraphQL.User#
-     * @function dislikes
-     * @summary Renvoie les groupes qu'il veut cacher
-     * @return {Promise(Group[])}
-     * @rights connectedOrOnplatal
-     * @async
-     */
-    async dislikes(args, context: Context, info): Promise<Group[]> {
-        throw "Not implemented";
-    }
-
     /**
      * @memberof GraphQL.User#
      * @function questionsFromUser
@@ -406,7 +396,14 @@ export class User {
      * @async
      */
     async questionsFromUser(args, context: Context, info): Promise<Question[]> {
-        await this.m_loader.load();
-        throw "Not implemented";
+        let questions = await context.models.message.getQuestionsFromUser(this.uid);
+        let res = new Array<Question>();
+
+        for(let q of questions) {
+            if(await context.models.auth.isViewer(await q.getRecipient()))
+                res.push(q);
+        }
+
+        return res;
     }
  }
diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts
index 6165fdd..f0c6e82 100644
--- a/src/graphql/resolvers.ts
+++ b/src/graphql/resolvers.ts
@@ -211,6 +211,15 @@ export const resolvers = {
             return context.models.group.getAllSimpleGroups(visibleGroupCollection.simpleGroups);
         },
 
+        // @rights connected
+        dislikes: async function (root, args, context: Context): Promise<Group[]> {
+            if(context.models.auth.isAuthenticated()) {
+                let groups = await context.models.user.getDislikes();
+                return context.models.group.getAllGroupsBySet(groups);
+            }
+            throw new AuthenticationError("Not connected");
+        },
+
         // TOL
         // @rights connectedOrOnplatal
         searchTOL: async function (root, args, context: Context): Promise<User[]> {
diff --git a/src/graphql/typeDefs/actions.graphql b/src/graphql/typeDefs/actions.graphql
index ae91a48..91c8713 100644
--- a/src/graphql/typeDefs/actions.graphql
+++ b/src/graphql/typeDefs/actions.graphql
@@ -33,6 +33,7 @@ type Query {
     # Correspondrait au sous-champ "viewerOf" de User, volontairement non-défini comme tel. Tous les autres cas de figure sont couverts par les sous-champs "<permission>Of" de User
     allGroups: [Group]
     allSimpleGroups: [SimpleGroup]
+    dislikes: [Group] # Groupes que l'utilisateur veut cacher
 
     # TOL
     searchTOL(
diff --git a/src/graphql/typeDefs/objects.graphql b/src/graphql/typeDefs/objects.graphql
index 48e8335..766ac7b 100644
--- a/src/graphql/typeDefs/objects.graphql
+++ b/src/graphql/typeDefs/objects.graphql
@@ -54,7 +54,6 @@ type User {
     adminOf: [Group] # Admin strict
     inheritedAdminOf: [Group] # Admin hérité
     likes: [Group] # Groupes dont l'utilisateur est sympathisant (purement indicatif, pas d'influence sur les niveaux de droit)
-    dislikes: [Group] # Groupes que l'utilisateur veut cacher
 
     # Les Message dont il est l'auteur
     questionsFromUser: [Question] # Les seuls Messages publics où `authors` est de type User
diff --git a/test/resolvers/data/onplatal/users.ts b/test/resolvers/data/onplatal/users.ts
index 1981e37..32ad47e 100644
--- a/test/resolvers/data/onplatal/users.ts
+++ b/test/resolvers/data/onplatal/users.ts
@@ -141,6 +141,22 @@ export const tests: TestSet = {
             }
         },*/
 
+        {
+            description: "dislikes not authorized",
+            query: `
+                query {
+                    dislikes {
+                        gid
+                    }
+                }
+            `,
+            result: {
+                "data": {
+                    "dislikes": null
+                }
+            }
+        },
+
         {
             description: "firstName, lastName, nickName",
             query: `
@@ -203,5 +219,197 @@ export const tests: TestSet = {
                 }
             }
         },
+
+        {
+            description: "inheritedMemberOf",
+            query: `
+                query {
+                    user(uid: "oliver.facklam") {
+                        inheritedMemberOf {
+                            gid
+                        }
+                    }
+                }
+            `,
+            result: {
+                "data": {
+                    "user": {
+                        "inheritedMemberOf": [
+                            {
+                                "gid": "br"
+                            },
+                            {
+                                "gid": "faerix"
+                            },
+                            {
+                                "gid": "xy"
+                            },
+                            {
+                                "gid": "chorale"
+                            },
+                            {
+                                "gid": "subaisse"
+                            },
+                            {
+                                "gid": "sport_judo"
+                            },
+                            {
+                                "gid": "on_platal"
+                            },
+                            {
+                                "gid": "formation_x"
+                            },
+                            {
+                                "gid": "promo_2017"
+                            },
+                            {
+                                "gid": "promo_x2017"
+                            },
+                            {
+                                "gid": "groupe_sigma"
+                            },
+                            {
+                                "gid": "root_br"
+                            },
+                            {
+                                "gid": "artificial_intelligence"
+                            },
+                            {
+                                "gid": "federez"
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+
+        {
+            description: "speakerOf",
+            query: `
+                query {
+                    user(uid: "oliver.facklam") {
+                        speakerOf {
+                            gid
+                        }
+                    }
+                }
+            `,
+            result: {
+                "data": {
+                    "user": {
+                        "speakerOf": [
+                            {
+                                "gid": "groupe_sigma"
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+
+        {
+            description: "inheritedAdminOf",
+            query: `
+                query {
+                    user(uid: "oliver.facklam") {
+                        inheritedAdminOf {
+                            gid
+                        }
+                    }
+                }
+            `,
+            result: {
+                "data": {
+                    "user": {
+                        "inheritedAdminOf": [
+                            {
+                                "gid": "groupe_sigma"
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+
+        {
+            description: "likes",
+            query: `
+                query {
+                    user(uid: "timothee.darcet") {
+                        likes {
+                            gid
+                        }
+                    }
+                }
+            `,
+            result: {
+                "data": {
+                    "user": {
+                        "likes": [
+                            {
+                                "gid": "impro"
+                            },
+                            {
+                                "gid": "montagne"
+                            },
+                            {
+                                "gid": "x-courseaularge"
+                            },
+                            {
+                                "gid": "wep"
+                            },
+                            {
+                                "gid": "xonthemun"
+                            },
+                            {
+                                "gid": "skiclub"
+                            },
+                            {
+                                "gid": "bspp"
+                            },
+                            {
+                                "gid": "ratemyclass"
+                            },
+                            {
+                                "gid": "ratatouille"
+                            },
+                            {
+                                "gid": "badas"
+                            },
+                            {
+                                "gid": "aventuriers"
+                            },
+                            {
+                                "gid": "cordix"
+                            },
+                            {
+                                "gid": "ionsat"
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+
+        {
+            description: "questionsFromUser empty (only for viewers of groups)",
+            query: `
+                query {
+                    user(uid: "timothee.darcet") {
+                        questionsFromUser {
+                            title
+                            content
+                        }
+                    }
+                }
+            `,
+            result: {
+                "data": {
+                    "user": {
+                        "questionsFromUser": []
+                    }
+                }
+            }
+        },
     ]
 }
\ No newline at end of file
-- 
GitLab