From aeee9f5dc135117f8c3607c3a9e41586c6a4b0a0 Mon Sep 17 00:00:00 2001 From: Guillaume WANG <guillaume.wang@polytechnique.edu> Date: Mon, 22 Oct 2018 09:45:36 +0200 Subject: [PATCH] update schema wish-list --- src/graphql/typeDefs/actions_wish_list.gql | 159 ----------- .../typeDefs/actions_wish_list.graphql | 257 ++++++++++++++++++ ...ish_list.gql => objects_wish_list.graphql} | 141 +++++++--- 3 files changed, 356 insertions(+), 201 deletions(-) delete mode 100644 src/graphql/typeDefs/actions_wish_list.gql create mode 100644 src/graphql/typeDefs/actions_wish_list.graphql rename src/graphql/typeDefs/{objects_wish_list.gql => objects_wish_list.graphql} (50%) diff --git a/src/graphql/typeDefs/actions_wish_list.gql b/src/graphql/typeDefs/actions_wish_list.gql deleted file mode 100644 index 1204069..0000000 --- a/src/graphql/typeDefs/actions_wish_list.gql +++ /dev/null @@ -1,159 +0,0 @@ -# ce fichier en .gql sert juste a forcer VSCode a colorier correctement le langage graphQL. -# son contenu sera copie dans actions.js tout simplement - -/** - * @file définit les types query et mutation, points d'entree du schéma graphQL. ce fichier est la wish list de kadabra (qui veut avoir un schema clair pour travailler sereinement sur le front) - * @author akka vodol, kadabra -*/ - -# hawkspar->akka ; pas clair - -const RootQuery = ` - # Requêtes - - type Query { - # User queries de base - user(uid:ID!): User - - # Group queries de base - group(uid:ID!): Group - simpleGroup(uid:ID!): SimpleGroup - metaGroup(uid:ID!): MetaGroup - - # Message queries de base - message(id:ID!): Message - announcement(id:ID!): Announcement - event(id:ID!): Event - privatePost(id:ID!): PrivatePost - question(id:ID!): Question - answer(id:ID!): Answer - - # Request queries de base - request(id:ID!): Request - userJoinGroupRequest(id:ID!): UserJoinGroup - groupCoauthorEventRequest(id:ID!): GroupCoauthorEvent - - - - # Messages créés par un AuthorUnion=Group|[Group]|User - messagesFromGroup(uid:ID!): [Message] - announcementsFromGroup(uid:ID!): [Announcement] - eventsFromGroup(uid:ID!): [Event] - privatepostsFromUser(uid:ID!): [Post] - questionsFromUser(uid:ID!): [Question] - answersFromGroup(uid:ID!): [Answer] - - # Messages adressés à un Group - messagesToGroup(uid:ID!): [Message] - announcementsToGroup(uid:ID!): [Announcement] - eventsToGroup(uid:ID!): [Event] - privatepostsToGroup(uid:ID!): [Post] - questionsToGroup(uid:ID!): [Question] - answersToGroup(uid:ID!): [Answer] - - - - # Toutes les Requests auxquelles un groupe doit répondre - requestsToGroup(uid:ID!): [Request] - userJoinGroupRequestsToGroup(id:ID!): UserJoinGroup - groupCoauthorEventRequestsToGroup(id:ID!): GroupCoauthorEvent - - - - # Tous les xxx visibles par un utilisateur - allGroups(uid:ID!): [Group] - allSimpleGroups(uid:ID!): [SimpleGroup] - allMetaGroups(uid:ID!): [MetaGroup] - - allMessages(uid:ID!): [Message] - allAnnouncements(uid:ID!): [Announcement] - allEvents(uid:ID!): [Event] - - - - # TOL - searchTOL( - givenName: String, - lastName: String, - nickname: String, - nationality: String, - school: String, - promotion: String, - groups: String, - studies: String, - sport: String, - phone: String, - mail: String, - address: String, - ip: String - ): [User!] - - } -`; - -const RootMutation = ` - # Mutations - - type Mutation { - # Par rapport à un groupe donné, un user peut avoir différents niveaux de droits : - # none : aucun droit (typiquement, une connection ou l'utilisateur ne s'est pas authentifie) - # viewer : le user sait que le groupe existe et a accès aux infos de base, mais rien de plus - # member : le user est membre du groupe et a acces aux Message dont le groupe est auteur ou destinataire - # speaker : le user peut parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements - # admin : le user a tous les droits sur le groupe - - # Viewer mutations - requestJoin(userid: ID!, groupuid: ID!): Boolean - - - # Member mutations - leave(userid: ID!, groupuid: ID!): Boolean - postInGroup(userid: ID!, groupuid: ID!, postContent: String) - - - - # Speaker mutations - - writeAnnouncement( - from: uid!, - to: [uid!], - title: String, - date: String, - content: String - ) - writeEvent( - from: ID!, - to: [uid!], - title: String, - date: String, - content:String - ) - - - - # Admin mutations - - createSubgroup( - from: ID!, - newUid: ID, - name: String!, - website: String, - description: String, - school: String - ): Group - - addUser(userid: ID!, groupuid: ID!): User - removeUser(userid: String!, groupuid: ID!): User - addAdmin(userid: String!, groupuid: ID!): User - removeAdmin(userid: String!, groupuid: ID!): User - - editGroup( - from: String!, - name: String, - website: String, - description: String, - school: String - ): Group - - } -`; \ No newline at end of file diff --git a/src/graphql/typeDefs/actions_wish_list.graphql b/src/graphql/typeDefs/actions_wish_list.graphql new file mode 100644 index 0000000..c1ad6f1 --- /dev/null +++ b/src/graphql/typeDefs/actions_wish_list.graphql @@ -0,0 +1,257 @@ +""" +@file Définit les types spéciaux Query et Mutation, points d'entrée du schéma GraphQL. + Ce fichier est la wish-list de kadabra (qui veut avoir un schéma clair pour travailler sereinement sur le front). +@author akka vodol, kadabra +""" + +type Query { + # User queries de base + user(uid:ID!): User + + # Group queries de base + group(gid:ID!): Group + simpleGroup(gid:ID!): SimpleGroup + metaGroup(gid:ID!): MetaGroup + + # Message queries de base + message(id:ID!): Message + announcement(id:ID!): Announcement + event(id:ID!): Event + privatePost(id:ID!): PrivatePost + question(id:ID!): Question + answer(id:ID!): Answer + + # Request queries de base + request(rid:ID!): Request + userJoinGroupRequest(rid:ID!): UserJoinGroup + groupCoauthorEventRequest(rid:ID!): GroupCoauthorEvent + + + + # Tous les Messages visibles par un utilisateur (dont le uid, et donc les autorisations, est passé par context) + allMessages: [Message] + allAnnouncements: [Announcement] + allEvents: [Event] + # Tous les Groupes visibles par un utilisateur. + # 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] + + + + # Toutes les Requests auxquelles un groupe doit répondre + requestsToGroup(gid:ID!): [Request] + userJoinGroupRequestsToGroup(gid:ID!): UserJoinGroup + groupCoauthorEventRequestsToGroup(gid:ID!): GroupCoauthorEvent + + + + # TOL + searchTOL( + givenName: String, + lastName: String, + nickname: String, + nationality: String, + school: String, + promotion: String, + groups: String, + studies: String, + sport: String, + phone: String, + mail: String, + address: String, + ip: String + ): [User!] + +} + +type Mutation { + """ + Par rapport à un groupe donné, un user peut avoir différents niveaux de droits : + - none : sait que le groupe existe, mais aucun autre droit (typiquement, une connection où l'utilisateur ne s'est pas authentifié) + - viewer : le user a aussi accès à l'activité publique du groupe : frontpage, Q&A, liste des membres, speakers et admins + - member : le user a aussi acces à l'activité interne du groupe : les PrivatePost, ainsi que les Message dont le groupe est auteur ou destinataire + - speaker : le user peut aussi parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements + - admin : le user a tous les droits sur le groupe + + Un des rôles du *graphe organique des groupes* est de définir le niveau de droit des users pour chaque groupe. + D'abord, petit détail de terminologie : les cinq niveaux de droits sont inclus les uns dans les autres (un speaker est aussi un viewer, par ex.) + - Les conditions pour qu'un user soit membre, speaker ou admin sont claires, puisque cette information est stockée directement en BDD. + - Un user non-membre est viewer du groupe G : + - s'il est membre d'un groupe immédiatement parent de G (one-edge-down visibility), ou + - s'il est membre d'un groupe faisant partie du champ "visibilityEdge" de G + - s'il est membre d'un métagroupe dont G est membre (implicit visibility-edges). + - Dans tous les autres cas, le user a le niveau de droits "none". + + L'autre rôle du *graphe organique des groupes* est de permettre l'administration "en cascade" des groupes enfants. + Si un groupe est le parent d'un autre, alors les admins du groupe parent peuvent se déclarer admins du groupe enfant. Exemples : + - BR est parent de Chocapix. L'admin de Chocapix est parti en vacances. L'admin de BR peut se déclarer admin de Chocapix et faire ce qu'il a à faire, sans attendre le retour du respo Chocapix. + - Cotisants-Kès est parent de Troll'X. Troll'X fait n'importe quoi (en floodant de Messages par ex). Les admins de Cotisants-Kès (les kessiers) peuvent se déclarer admin de Troll'X et stopper les dégâts. + + Remarque sur speaker : + Il s'agit d'un nouveau niveau de droit par rapport à Frankiz 3.0 ; il n'est pas implémenté dans le LDAP frankiz. + Par conséquent, il est probable que, au début du moins, on impose que speaker=admin, pour pouvoir continuer à utiliser ce LDAP. + + Les mutations ci-dessous sont divisées en quatre, selon le niveau de droit requis pour pouvoir les appeler. + Les Mutations concernant les *Requests* suivent à peu près toutes le schéma suivant : + - <typeAuteur>Request<NatureDeLaRequest>(auteur, destinataire, comment): Request + - accept<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean + - refuse<NatureDeLaRequest>Request(request: ID!, comment: String): Boolean + - le paramètre est le rid de la Request à accepter ou refuser + - seul les admins du groupe destinataire peuvent accepter ou refuser une Request + Les Mutations concernant les *Messages* suivent à peu près toutes le schéma suivant : + - create<TypeDeMessage>(auteur, destinataire, title, content, etc.): <TypeDeMessage> + - edit<TypeDeMessage>(<typeDeMessage>ToEdit: ID!): <TypeDeMessage> + - remove<TypeDeMessage>(<typeDeMessage>ToRemove: ID!): Boolean + - = l'auteur supprime le message + - pour les Messages où l'auteur est un utilisateur, seul l'auteur a le droit de remove son Message + - pour les Messages où l'auteur est un groupe, n'importe quel admin du groupe (ou speaker selon le cas) a le droit de remove le Message + - censor<TypeDeMessage>(<typeDeMessage>ToCensor: ID!): Boolean + - = le groupe destinataire supprime le message + - n'importe quel admin du groupe a le droit de censurer un Message qui lui est adressé + - (le destinataire est un Group pour tous les Messages) + """ + + # Viewer mutations + likeGroup(groupId: ID!): Boolean # devenir sympathisant + userParticipate( + forEvent: ID! + ): Boolean + userUnparticipate( + forEvent: ID! + ): Boolean + + userRequestJoinGroup(toGroup: ID!, comment: String): Request + + createQuestion( + toGroup: ID!, + title: String, + content: String, + ): Question + editQuestion( + questionToEdit: ID!, + title: String, + content: String, + ): Question + removeQuestion( + questionToRemove: ID! + ): Question + + # Member mutations + userLeaveGroup(groupId: ID!): Boolean + + createPrivatePost( + toGroup: ID!, + title: String, + content: String + ): PrivatePost + editPrivatePost( + privatePostToEdit: ID!, + title: String, + content: String + ): PrivatePost + removePrivatePost( + privatePostToRemove: ID! + ): PrivatePost + + # Speaker mutations + writePostsSummary(forGroup: ID!, content: String): Boolean + + groupRequestCoauthorEvent( + fromGroup: ID!, + toGroup: ID!, + forEvent: ID!, + comment: String + ): Request + + createAnnouncement( + fromGroup: ID!, + toGroups: [ID!], + title: String, + content: String, + forEvent: ID + ): Announcement + editAnnouncement( + announcementToEdit: ID!, + title: String, + content: String, + forEvent: ID + ): Announcement + removeAnnouncement( + announcementToRemove: ID! + ): Boolean + + createEvent( + fromGroup: ID!, + toGroups: [ID!], + title: String, + content: String, + location: String, + startTime: String, + endTime: String, + forAnnouncement: ID + ): Event + editEvent( + eventToEdit: ID!, + title: String, + content:String, + location: String, + startTime: String, + endTime: String, + forAnnouncement: ID + ): Event + removeEvent( + eventToRemove: ID! + ): Boolean + + createAnswer( + forQuestion: ID!, + title: String, + content: String + ) + editAnswer( + answerToEdit: ID!, + title: String, + content: String + ) + removeAnswer( + answerToRemove: ID! + ): Boolean + + # Admin mutations + createSubgroup( + fromGroup: ID!, + subGid: ID, + subName: String!, + subDescription: String, + subMail: String, + subWebsite: String, + subSchool: String + ): Group + becomeAdmin(forGroup: ID!): Boolean # requiert que l'utilisateur soit admin du groupe parent de forGroup + + makeAdmin(forGroup: ID!, userId: ID!): User + unmakeAdmin(forGroup: ID!, userId: ID!): User + + editGroup( + forGroup: ID!, + name: String, + description: String, + mail: String, + website: String, + school: String + ): Group + + groupRequestJoinMetagroup(fromGroup: ID!, toMetagroup: ID!, comment: String): Request + + acceptUserJoinRequest(request: ID!, comment: String): Boolean + acceptGroupJoinRequest(request: ID!, comment: String): Boolean + refuseUserJoinRequest(request: ID!, comment: String): Boolean + refuseGroupJoinRequest(request: ID!, comment: String): Boolean + + censorQuestion(questionToCensor: ID!): Boolean + censorPrivatePost(privatePostToCensor: ID!): Boolean + censorAnnouncement(announcementToCensor: ID!): Boolean + censorEvent(eventToCensor: ID!): Boolean + censorAnswer(answerToCensor: ID!): Boolean + +} diff --git a/src/graphql/typeDefs/objects_wish_list.gql b/src/graphql/typeDefs/objects_wish_list.graphql similarity index 50% rename from src/graphql/typeDefs/objects_wish_list.gql rename to src/graphql/typeDefs/objects_wish_list.graphql index 3aa5bee..a963059 100644 --- a/src/graphql/typeDefs/objects_wish_list.gql +++ b/src/graphql/typeDefs/objects_wish_list.graphql @@ -1,4 +1,28 @@ -# Un utilisateur +""" +@file Définit les types du schéma GraphQL. Il y en a quatre catégories : User, Group, Message, Request. + Ce fichier est la wish-list de kadabra (qui veut avoir un schéma clair pour travailler sereinement sur le front). + +Conseils généraux pour toute tentative d'amélioration du schéma : + - mettre un -s aux champs array [SomeType]. + - ... et même aux champs union *pouvant être* des array. + - ne pas rajouter un champ si on peut prévoir qu'il ne sera jamais utilisé. + - ne pas rajouter un champ si cela complexifie trop la gestion des autorisations. + - (par exemple pour User, tous les champs doivent pouvoir être vus par toute personne ayant accès au TOL.) + - dans ce cas créer un nouveau query, tout simplement. + - respecter la convention : + - uid = user id + - gid = group id + - id = message id + - rid = request id + - choisir des noms clairs, précis, concis. + - commenter lorsque c'est pertinent, également en étant clair, précis, concis. + +@author akka vodol, kadabra +""" + +""" +Un utilisateur +""" type User { uid: ID! @@ -16,17 +40,22 @@ type User { address: [String] # Adresse(s) de l'utilisateur (numero de casert par exemple) # Ses interactions avec des groupes - groups: [SimpleGroup] # Groupes dont l'utilisateur est membre - likes: [Group] # Groupes que l'utilisateur sympathise + memberOf: [SimpleGroup] # Groupes dont l'utilisateur est membre + speakerOf: [SimpleGroup] + adminOf: [Group] + likes: [Group] # Groupes dont l'utilisateur est sympathisant (purement indicatif, pas d'influence sur les niveaux de droit) + + # Les Message dont il est l'auteur + questionsFromUser: [Question] # Les seuls Messages publics où `authors` est de type User } """ -L'interface Group représente les deux types de groupes implémentés dans Sigma : les groupes -simples, dont les membres sont des utilisateurs, et les métagroupes, dont les membres sont -des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA). +L'interface Group représente les deux types de groupes implémentés dans Sigma : +- les groupes simples, dont les membres sont des utilisateurs, et +- les métagroupes, dont les membres sont des groupes simples (tel que Federez, dont les membres incluent le BR et DaTA). """ interface Group { - uid: ID! + gid: ID! createdAt: String! # Date et heure de création du groupe. updatedAt: String! # Date et heure de mise a jour des informations du groupe. @@ -37,51 +66,55 @@ interface Group { # Pour le contacter mail: String website: String + + # Administrateurs, à contacter directement en cas de problème + admins: [User] } # Le groupe de base, dont les membres sont des utilisateurs : binets, Kès... type SimpleGroup implements Group { - uid: ID! + gid: ID! createdAt: String! updatedAt: String! - name: String + name: String! description: String mail: String website: String - # Admin, membres, sympathisants du groupe - admins: [User] + # Admins, speakers (respos com), membres, sympathisants du groupe members: [User] + speakers: [User] + admins: [User] likers: [User] # Graphe organique des groupes parent: SimpleGroup # Groupe parent children: [SimpleGroup] # Groupes enfants memberOfMeta: [MetaGroup] - visibilityEdge: [Group] # visible par des groupes en plus du graphe organique + visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique school: String # École d'origine du groupe (pour information) # Activité publique du groupe - announcementsFromGroup: [Announcement] # annonces écrites par le groupe - announcementsToGroup: [Announcement] # annonces adressées au groupe - eventsFromGroup: [Event] - eventsToGroup: [Event] frontPage: String # page d'accueil du groupe, en markdown questions: [Question] answers: [Answer] # permet d'obtenir les questions qui ont eu une réponse # Activité interne + announcementsFromGroup: [Announcement] # annonces écrites par le groupe + announcementsToGroup: [Announcement] # annonces adressées au groupe + eventsFromGroup: [Event] + eventsToGroup: [Event] posts: [Post] postsSummary: String # récapitulatif de l'activité interne du groupe, en markdown } # Un groupe dont les membre sont d'autres groupes type MetaGroup implements Group { - uid: ID! + gid: ID! createdAt: String! updatedAt: String! - name: String + name: String! description: String mail: String website: String @@ -90,7 +123,7 @@ type MetaGroup implements Group { admins: [User] members: [SimpleGroup] # Par choix de paradigme, on veut éviter d'avoir des méta-méta-groupes. - visibilityEdge: [Group] # visible par des groupes en plus du graphe organique + visibilityEdges: [Group] # se rendre visible par des groupes en plus du graphe organique } union AuthorUnion = Group | [Group] | User @@ -111,7 +144,7 @@ interface Message { content: String! authors: AuthorUnion - recipient: RecipientUnion # destinataire du Message. forcement (un ou plusieurs) Group + recipients: RecipientUnion # destinataire du Message. forcement (un ou plusieurs) Group } # Annonce effectuée par un ou plusieurs groupes. @@ -125,8 +158,8 @@ type Announcement implements Message { authors: [Group] recipients: [Group] - importance: Int # TODO: mettre un commentaire pour expliquer - views: Int # TODO mettre un commentaire pour expliquer + importance: Int # importance de cette Announcement, sur une échelle de [??] à [??] (TODO) + views: Int # nombre de vues # Si cette Announcement annonce un événement, référence le Event. Sinon null. forEvent: Event @@ -147,8 +180,8 @@ type Event implements Message { startTime: String! endTime: String! - # Personnes qui participent à l'événement. - participatingGroups: [Group] + # Personnes et groupes qui participent à l'événement. + participatingGroups: [Group] # contributeurs mais pas organisateurs (par ex, Fanfare à une proj' JTX) participatingUsers: [User] # Si cet Event a été annoncé par un Announcement, le référence. Sinon null. @@ -164,7 +197,7 @@ type PrivatePost implements Message { content: String! authors: User - recipient: Group + recipients: Group } # Question posée par un user à un groupe @@ -176,7 +209,7 @@ type Question implements Message { content: String! authors: User - recipient: Group + recipients: Group # Référence la réponse donnée par le groupe à cette Question. Si pas encore répondu, null. forAnswer: Answer @@ -191,38 +224,62 @@ type Answer implements Message { content: String! authors: Group - recipient: Group + recipients: Group # doit être le même que authors # La question à laquelle cette Answer répond. Non-nullable bien sûr forQuestion: Question! } +""" +Différents types de requêtes peuvent être adressées à un groupe. Elles sont stockées en BDD en attente d'être traitées. +Par exemple (le plus évident) demander à devenir membre, mais il existe aussi d'autres cas de figure. +On peut les voir comme des Mutations potentielles : en cas de validation de la requête, des entrées de la BDD seront modifiées. +Seuls les admins d'un Group (qu'il soit Simple ou Meta) ont le droit de valider ou refuser une requête. +Les différents types implémentant Request représentent des types de requête : +- UserJoinGroup: un User demande à devenir membre d'un SimpleGroup +- GroupCoauthorEvent: un groupe demande à devenir (co-)organisateur d'un événement *déjà existant* + +TODO : +Il n'y a pas de type pour représenter la réponse à une Request. A première vue on peut penser qu'on n'en a pas besoin. +Cependant il faut bien qu'un utilisateur du frontend puisse dire au serveur back qu'il souhaite valider ou refuser une Request. +Or, on a choisi de faire passer absolument toutes les communications frontend/backend par GraphQL... +Il faut donc intégrer les réponses aux Requests au schéma. +""" # Emetteur possible d'une Request union RequesterUnion = Group | User interface Request { - id: ID! - message: String # court message accompagnant la demande + rid: ID! + comment: String # court message accompagnant la demande - from: RequesterUnion # Émet la demande - to: Group # Reçoit la demande + from: RequesterUnion! # Émet la demande + to: Group! # Reçoit la demande } -# Demande d'un utilisateur désirant rejoindre le groupe. +# Un utilisateur demande à devenir membre d'un groupe (simple bien sûr). type UserJoinGroup implements Request{ - id: ID! - message: String + rid: ID! + comment: String - from: User - to: Group + from: User! + to: SimpleGroup! +} + +# Un groupe simple demande à devenir membre d'un méta-groupe +type GroupJoinMetagroup implements Request{ + rid: ID! + comment: String + + from: SimpleGroup! + to: MetaGroup! } -# Invite un groupe à co-organiser son événement (et pas l'inverse!) +# Un Group demande à devenir (co-)author d'un Event *déjà existant*. type GroupCoauthorEvent implements Request{ - id: ID! - message: String + rid: ID! + comment: String - from: Group # Groupe authorant l'évènement et lançant l'invitation - to: Group # Groupe récipiendaire de l'invitation à co-hoster l'événement - forEvent: Event + from: Group! # Groupe souhaitant l'évènement et lançant l'invitation + to: Group! # un des Groupes organisant l'événement (erreur sinon) + forEvent: Event! } -- GitLab