diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f39e983e4a83f85ee528fc03fa7f92dc6c59a6c..16384a215f2e18e40da1a463e4ff19580ef4b12f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,8 @@ La structure générale du projet est détaillée ci-dessous ; pas d'inquiétude - [`build`](../build) : dépendances gérées automatiquement ; - bundle.js est un monstrueux fichier généré automatiquement qui gère nos dépendances. - [`db`](../db) : base de donnée sigma ; - - knex_router.js charge la configuration précisée dans knexfile.js, + - knexfile.js donne les options de configuration de knex + - knex_router.js charge la configuration précisée dans knexfile.js, - [`migrations`](../db/migrations) stocke l'ensemble des migrations knex ; c'est un équivalent d'un répertoire git pour la BDD sigma (du coup seulement les groupes pas les individus), - [`seeds`](../db/seeds) : stocke les éléments de générations des BDD ; c'est un ensemble de scripts. - [`doc`](.) : documentation ; diff --git a/configfile_doc.json b/configfile_doc.json index ca587bd069094700cd05aefdcacfcf6d7a9d127a..3c4db54b4e44b38898249152e450588b681da74e 100644 --- a/configfile_doc.json +++ b/configfile_doc.json @@ -3,7 +3,7 @@ "plugins": ["plugins/markdown", "plugins/summarize", "node_modules/jsdoc-babel"], "recurseDepth": 5, "source": { - "include": ["./src","./db","./knexfile.js","./README.md"], + "include": ["./src","./db","./README.md"], "exclude": ["./db/migrations","./db/seeds"], "includePattern": ".+\\.(js|ts)(doc|x)?$", "excludePattern": "(^|\\/|\\\\)_" diff --git a/db/knex_router.js b/db/knex_router.js index 9f8a0629ac28af46f4d50ee85f54ca4249c247fd..8911c2eae70bb922c2c31252cc2c91ce6c6f7711 100644 --- a/db/knex_router.js +++ b/db/knex_router.js @@ -4,8 +4,8 @@ require('dotenv').config(); const environment = process.env.TARGET_ENV || 'development'; -const config = require('../knexfile')[environment]; +const config = require('./knexfile')[environment]; console.log("Running Knex configuration '%s'", environment); -module.exports = require('knex')(config); \ No newline at end of file +module.exports = require('knex')(config); diff --git a/knexfile.js b/db/knexfile.js similarity index 70% rename from knexfile.js rename to db/knexfile.js index a9896f7d9d1f420a6940e54f32a7b5e099e6b2d4..dc6a3c7b5f3bf27855823251f975be9c346e545e 100644 --- a/knexfile.js +++ b/db/knexfile.js @@ -3,6 +3,9 @@ * d'un utilisateur sur le serveur qui a les droits appropriés, et son mot de passe. * * Le fichier précise également où stocker les fichiers de migrations Knex ainsi que les _seeds_. + * + * Plus précisément, ce fichier sert de fichier de configuration à la commande `knex` (en lignes de commande) + * et c'est knex_router.js qui donne la configuration de knex.js à proprement parler (i.e. les méthodes knex.insert(...) dans le code javascript) * @author manifold */ require('dotenv').config(); @@ -11,10 +14,10 @@ const path = require('path'); const knexConfig = { migrations: { tableName: 'knex_migrations', - directory: path.resolve('./db/migrations') + directory: path.resolve('./migrations' /*'./db/migrations'*/) }, seeds: { - directory: path.resolve('./db/seeds') + directory: path.resolve('./seeds' /*'./db/seeds'*/) } }; @@ -24,7 +27,7 @@ module.exports = { connection: { host: 'localhost', user: process.env.DB_USER || 'sigma', - password: process.env.DB_PASSWD || 'password', + password: process.env.DB_PASSWD || 'sigmapw', database: 'sigma_dev', charset: 'utf8' }, diff --git a/db/migrations_v2/20181110100101_create_groups.js b/db/migrations_v2/20181110100101_create_groups.js new file mode 100644 index 0000000000000000000000000000000000000000..7f56bb994110ae4777981444a50dbfd8ec481dd7 --- /dev/null +++ b/db/migrations_v2/20181110100101_create_groups.js @@ -0,0 +1,24 @@ + +// Special groups (implemented by the seeds, specified as a note here) : +// 'root' group, ancestor of all SimpleGroups. (simple group) +// 'noone' group, direct child of 'root' group. Serves as recipient of all unpublished Messages +// (e.g. drafts, messages with deleted recipient...) (simple group) + +exports.up = function (knex, Promise) { + return knex.schema.createTable('groups', function (table) { + table.string('gid',128).primary().notNullable(); //primary key *human readable*! should, but does not need to, match exactly column 'name' + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + table.enum('type', ['simple', 'meta', 'error']).notNullable().defaultTo('error'); + + table.string('parent_gid',128).notNullable().defaultTo('root') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if parent group is deleted, become child of root group + table.string('name').notNullable(); + table.string('website'); //.defaultTo(''); <-- default to null instead + table.text('description').defaultTo('Hello world! <Insert group description here>'); + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('groups'); +}; diff --git a/db/migrations_v2/20181110100202_create_childtable_simple_groups.js b/db/migrations_v2/20181110100202_create_childtable_simple_groups.js new file mode 100644 index 0000000000000000000000000000000000000000..ead227bff989ece79ccb560ca093c8173333e8c7 --- /dev/null +++ b/db/migrations_v2/20181110100202_create_childtable_simple_groups.js @@ -0,0 +1,23 @@ +//Après cette migration, plus rien n'est sensé être mis directement dans la table groups + +let schoolEnum = [ + 'polytechnique', + 'ensta', + 'supoptique', + 'ensae', + 'centrale', + 'enscachan', + 'otherschool', + 'notaschool' +]; + +exports.up = function (knex, Promise) { + return knex.schema.createTable('simple_groups', function (table) { + table.inherits('groups'); + table.enum('school', schoolEnum).notNullable(); + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('simple_groups'); +}; diff --git a/db/migrations_v2/20181110100303_create_childtable_meta_groups.js b/db/migrations_v2/20181110100303_create_childtable_meta_groups.js new file mode 100644 index 0000000000000000000000000000000000000000..44108910206db39ad71c6bc3af7ea4689c6cdb0f --- /dev/null +++ b/db/migrations_v2/20181110100303_create_childtable_meta_groups.js @@ -0,0 +1,10 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('meta_groups', function (table) { + table.inherits('groups'); + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('meta_groups'); +}; diff --git a/db/migrations_v2/20181110100404_create_associativetable_metagroup_memberships.js b/db/migrations_v2/20181110100404_create_associativetable_metagroup_memberships.js new file mode 100644 index 0000000000000000000000000000000000000000..d803d552d0f9edaf1d52a86e990f56344e0fb33a --- /dev/null +++ b/db/migrations_v2/20181110100404_create_associativetable_metagroup_memberships.js @@ -0,0 +1,34 @@ +// Using associative tables is the recommended way of representing many-to-many relationships. +//https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29 +//http://web.csulb.edu/colleges/coe/cecs/dbdesign/dbdesign.php?page=manymany.php + +let rightsEnum = [ + 'admin', + 'speaker', + 'member' +]; + +exports.up = function (knex, Promise) { + return knex.schema.createTable('metagroup_memberships', function (table) { + table.timestamps(true, true); + table.string('simple_group_gid',128).notNullable() + .references('gid').inTable('groups' /*'simple_groups'*/) + .onDelete('CASCADE'); //delete row from group_memberships if member is deleted + table.string('meta_group_gid',128).notNullable() + .references('gid').inTable('groups' /*'meta_groups'*/) + .onDelete('CASCADE'); + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + table.enum('rights', rightsEnum).notNullable(); //TODO: this is not used by GraphQL schema! + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('metagroup_memberships'); +}; diff --git a/db/migrations_v2/20181110110101_create_messages.js b/db/migrations_v2/20181110110101_create_messages.js new file mode 100644 index 0000000000000000000000000000000000000000..2efdc03360ea32debdbb21a3ecc76ba796338035 --- /dev/null +++ b/db/migrations_v2/20181110110101_create_messages.js @@ -0,0 +1,18 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('messages', function (table) { + table.increments('mid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('recipient',128).notNullable().defaultTo('noone') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if recipient is deleted, direct to the special "no-one" group + //'authors' can be a Group or a User, so specified in childtables. + table.string('title').notNullable(); + table.text('content'); + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('messages'); +}; diff --git a/db/migrations_v2/20181110110202_create_childtable_announcements.js b/db/migrations_v2/20181110110202_create_childtable_announcements.js new file mode 100644 index 0000000000000000000000000000000000000000..8c9e8c95cea9adc33272fa9589a46b3734978e2e --- /dev/null +++ b/db/migrations_v2/20181110110202_create_childtable_announcements.js @@ -0,0 +1,22 @@ + +exports.up = function(knex, Promise) { + return knex.schema.createTable('announcements', function (table) { + table.inherits('messages'); + //TODO: for now, we support only 1 author (where as graphql schema indicates support for [Group] authors) + table.string('author',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete message if author is deleted + table.integer('views').defaultTo(0); + /* + we cannot declare this column yet, as table 'events' is not yet created. + it will be declared in the migration for the 'events' childtable. + table.integer('for_event') + .references('mid').inTable('events') + .onDelete('SET NULL'); + */ + }); +}; + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('announcements'); +}; diff --git a/db/migrations_v2/20181110110303_create_childtable_events.js b/db/migrations_v2/20181110110303_create_childtable_events.js new file mode 100644 index 0000000000000000000000000000000000000000..7d9e11e5391af4678b0b7b9d468c16b4b5c532b7 --- /dev/null +++ b/db/migrations_v2/20181110110303_create_childtable_events.js @@ -0,0 +1,47 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('events', function (table) { + table.inherits('messages'); + table.string('location').notNullable(); + table.dateTime('start_time').notNullable(); + table.dateTime('end_time').notNullable(); //TODO: add a CHECK (https://www.postgresql.org/docs/current/ddl-constraints.html) that start_time < end_time + + //TODO: for now, we support only 1 author (where as graphql schema indicates support for [Group] authors) + table.string('author', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete message if author is deleted + table.integer('for_announcement') + .references('mid').inTable('messages' /*'announcements'*/) + .onDelete('SET NULL'); + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + + //TODO: add support for participatingGroups and participatingUsers from the graphql schema + + }).then( () => { + //update 'announcements' table by adding the 'for_event' column + knex.schema.table('announcements', function (table) { + table.integer('for_event') + .references('mid').inTable('messages' /*'events'*/) + .onDelete('SET NULL'); + }); + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('events'); +}; diff --git a/db/migrations_v2/20181110110404_create_childtable_private_posts.js b/db/migrations_v2/20181110110404_create_childtable_private_posts.js new file mode 100644 index 0000000000000000000000000000000000000000..b92bd25f9c6dd5e78650908287f136dca3c6f2a1 --- /dev/null +++ b/db/migrations_v2/20181110110404_create_childtable_private_posts.js @@ -0,0 +1,11 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('private_posts', function (table) { + table.inherits('messages'); + table.string('author').notNullable(); //must refer to a User's uid. + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('private_posts'); +}; diff --git a/db/migrations_v2/20181110110505_create_childtable_questions.js b/db/migrations_v2/20181110110505_create_childtable_questions.js new file mode 100644 index 0000000000000000000000000000000000000000..3cfcfbc72956ca71dcd5e0e326dc53a7b6eea9fb --- /dev/null +++ b/db/migrations_v2/20181110110505_create_childtable_questions.js @@ -0,0 +1,18 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('questions', function (table) { + table.inherits('messages'); + table.string('author').notNullable(); //must refer to a User's uid. + /* + we cannot declare this column yet, as table 'answers' is not yet created. + it will be declared in the migration for the 'answers' childtable. + table.integer('for_answer') + .references('mid').inTable('answers') + .onDelete('SET NULL'); //if answer is deleted, set for_answer to null + */ + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('questions'); +}; diff --git a/db/migrations_v2/20181110110606_create_childtable_answers.js b/db/migrations_v2/20181110110606_create_childtable_answers.js new file mode 100644 index 0000000000000000000000000000000000000000..2407c4961bad2914baab676dbf73cbc8321d1736 --- /dev/null +++ b/db/migrations_v2/20181110110606_create_childtable_answers.js @@ -0,0 +1,46 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('answers', function (table) { + table.inherits('messages'); + /* + we require that a GraphQL Answer's author field be the same as its recipient field. + since recipient is already known (defined in the 'messages' parent-table), + then we simply don't store answers' authors in database. + (another way would be to add equality constraints, but uselessly complex.) + table.string('author', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); + */ + table.integer('for_question').notNullable() + .references('mid').inTable('messages' /*'questions'*/) + .onDelete('CASCADE'); //if question is deleted, also delete the answer + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + + }).then(() => { + //update 'questions' table by adding the 'for_answer' column + knex.schema.table('questions', function (table) { + table.integer('for_answer') + .references('mid').inTable('messages' /*'answers'*/) + .onDelete('SET NULL'); //if answer is deleted, set for_answer to null + }); + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('answers'); +}; diff --git a/db/migrations_v2/20181110120101_create_requests.js b/db/migrations_v2/20181110120101_create_requests.js new file mode 100644 index 0000000000000000000000000000000000000000..b89276fa6688bef7aab14ac24579253c1fe06489 --- /dev/null +++ b/db/migrations_v2/20181110120101_create_requests.js @@ -0,0 +1,38 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('requests', function (table) { + table.increments('rid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('request_to',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete request from database if recipient group is deleted + //'request_from' can be a Group or a User, so specified in childtables. + table.text('request_comment'); + }); + + /* TODO: make a new migration to create these child tables + .then(()=>{ + return knex.schema.createTable('user_join_group', function (table) { + table.inherits('requests'); + table.string('useruid').notNullable(); + }).then(()=>{ + return knex.schema.createTable('group_join_event', function (table){ + table.inherits('requests'); + table.string('eventuid').notNullable(); + table.string('senderuid').notNullable(); + }).then(() => { + return knex.schema.createTable('your_group_host_event', function(table){ + table.inherits('requests'); + table.string('eventuid').notNullable(); + table.string('senderuid').notNullable(); + }); + }); + }); + }); + */ +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('requests'); +}; diff --git a/db/migrations_v2/20181110120202_create_childtable_user_join_group.js b/db/migrations_v2/20181110120202_create_childtable_user_join_group.js new file mode 100644 index 0000000000000000000000000000000000000000..2ea3b56dd66a9541afa3d190c768f05c60f226ab --- /dev/null +++ b/db/migrations_v2/20181110120202_create_childtable_user_join_group.js @@ -0,0 +1,11 @@ + +exports.up = function(knex, Promise) { + return knex.schema.createTable('user_join_group', function (table) { + table.inherits('requests'); + table.string('request_from').notNullable(); //must refer to a User's uid. + }); +}; + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('user_join_group'); +}; diff --git a/db/migrations_v2/20181110120303_create_childtable_group_join_metagroup.js b/db/migrations_v2/20181110120303_create_childtable_group_join_metagroup.js new file mode 100644 index 0000000000000000000000000000000000000000..9aa23e822682c22053f08d12df37d131fe0a04cd --- /dev/null +++ b/db/migrations_v2/20181110120303_create_childtable_group_join_metagroup.js @@ -0,0 +1,22 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('group_join_metagroup', function (table) { + table.inherits('requests'); + table.string('request_from',128).notNullable() + .references('gid').inTable('groups' /*'simple_groups'*/) + .onDelete('CASCADE'); //if requesting group is deleted, also delete request + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('group_join_metagroup'); +}; + diff --git a/db/migrations_v2/20181110120404_create_childtable_group_coauthor_event.js b/db/migrations_v2/20181110120404_create_childtable_group_coauthor_event.js new file mode 100644 index 0000000000000000000000000000000000000000..a2097480bf4fa8237eadea26e9f476cc7e78ba6c --- /dev/null +++ b/db/migrations_v2/20181110120404_create_childtable_group_coauthor_event.js @@ -0,0 +1,24 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('group_coauthor_event', function (table) { + table.inherits('requests'); + table.string('request_from',128).notNullable() + .references('gid').inTable('groups' /*'simple_groups'*/) + .onDelete('CASCADE'); //if requesting group is deleted, also delete request + /* + ^ as stated in https://www.postgresql.org/docs/10/static/ddl-inherit.html#DDL-INHERIT-CAVEATS, + "A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children." + it is not possible (with PostgresQL v10) to use both inheritance and foreign keys at once. + until it is, or a workaround is found, we simply don't maintain child-level referential integrity + and we hope all the rest will work, e.g. simple_group_gids will indeed refer to simple groups, and idem for meta_group_gids. + TODO: maybe, check if PostgresQL's latest version enables this feature? + */ + table.integer('for_event').notNullable() + .references('mid').inTable('messages' /*'events'*/) + .onDelete('CASCADE'); //if event is deleted, also delete request + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('group_coauthor_event'); +}; diff --git a/db/migrations_v2/readme.txt b/db/migrations_v2/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..f70d6788d84fc106d9dcb44be9353f8688524985 --- /dev/null +++ b/db/migrations_v2/readme.txt @@ -0,0 +1,13 @@ +Cette version du schéma de la BDD a été abandonnée lorsqu'on s'est rendus compte que, +du moins pour la version actuelle de PostgresQL (v11 en 2018-11), on ne peut pas utiliser +simultanément le inheritance et des foreign keys. Plus précisément, + +https://stackoverflow.com/questions/24360312/foreign-keys-table-inheritance-in-postgresql +> The short version: you can use foreign keys, or table inheritance, but not both. This isn't inherently impossible, it's just that it's technically quite difficult to implement unique indexes that span inherited tables in PostgreSQL in a fast, reliable manner. Without that, you can't have a useful foreign key. Nobody's successfully implemented it well enough for a patch adding support to be accepted into PostgreSQL yet. +> A foreign key can point to a table that is part of an inheritance hierarchy, but it'll only find rows in that table exactly. Not in any parent or child tables. To see which rows the foreign key sees, do a `SELECT * FROM ONLY` thetable. The ONLY keyword means "ignoring inheritance" and that's what the foreign key lookup will do. + +https://www.postgresql.org/docs/current/ddl-inherit.html#DDL-INHERIT-CAVEATS +A serious limitation of the inheritance feature is that indexes (including unique constraints) and foreign key constraints only apply to single tables, not to their inheritance children. This is true on both the referencing and referenced sides of a foreign key constraint. + +Il peut toutefois être utile de garder cette version du schéma de la BDD dans un coin, au cas où +la fonctionnalité inheritance + foreign keys arrive dans une prochaine version de PostgresQL... diff --git a/db/migrations_v3/20181110191614_create_groups.js b/db/migrations_v3/20181110191614_create_groups.js new file mode 100644 index 0000000000000000000000000000000000000000..a3eeea9ca84c7fa6a293e5f5190d210a1f0f99f2 --- /dev/null +++ b/db/migrations_v3/20181110191614_create_groups.js @@ -0,0 +1,38 @@ + +// Special groups (implemented by the seeds, specified as a note here) : +// 'root' group, ancestor of all SimpleGroups. (simple group) +// 'noone' group, direct child of 'root' group. Serves as recipient of all unpublished Messages +// (e.g. drafts, messages with deleted recipient...) (simple group) + +let schoolEnum = [ + 'polytechnique', + 'ensta', + 'supoptique', + 'ensae', + 'centrale', + 'enscachan', + 'otherschool', + 'notaschool' +]; + +exports.up = function (knex, Promise) { + return knex.schema.createTable('groups', function (table) { + table.string('gid',128).primary().notNullable(); //primary key *human readable*! should, but does not need to, match exactly column 'name' + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.enum('type', ['simple', 'meta', 'error']).notNullable().defaultTo('error'); + table.string('parent_gid',128).notNullable().defaultTo('root') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if parent group is deleted, become child of root group + + table.string('name').notNullable(); + table.string('website'); //.defaultTo(''); <-- default to null instead + table.text('description').defaultTo('Hello world! <Insert group description here>'); + + table.enum('school', schoolEnum).notNullable(); + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('groups'); +}; diff --git a/db/migrations_v3/20181110191757_create_associativetable_metagroup_memberships.js b/db/migrations_v3/20181110191757_create_associativetable_metagroup_memberships.js new file mode 100644 index 0000000000000000000000000000000000000000..fe3be82ebaf053cdaa401e42f168168e5074aa55 --- /dev/null +++ b/db/migrations_v3/20181110191757_create_associativetable_metagroup_memberships.js @@ -0,0 +1,31 @@ +// Using associative tables is the recommended way of representing many-to-many relationships. +//https://en.wikipedia.org/wiki/Many-to-many_%28data_model%29 +//http://web.csulb.edu/colleges/coe/cecs/dbdesign/dbdesign.php?page=manymany.php + +let rightsEnum = [ + 'admin', + 'speaker', + 'member' +]; + +exports.up = function (knex, Promise) { + return knex.schema.createTable('metagroup_memberships', function (table) { + table.timestamps(true, true); + + table.string('simple_group_gid',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete row from group_memberships if member is deleted + table.string('meta_group_gid',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); + // It is up to the user to check that the inserted gids do correspond + // to a simple_group and a meta_group (respectively). + // There is absolutely no check for this on the database side. + + table.enum('rights', rightsEnum).notNullable(); //TODO: this is not used by GraphQL schema! + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('metagroup_memberships'); +}; diff --git a/db/migrations_v3/20181110192012_create_messages_announcements.js b/db/migrations_v3/20181110192012_create_messages_announcements.js new file mode 100644 index 0000000000000000000000000000000000000000..654dd0e687d26d46ba63ad7b7dac773b88ced71b --- /dev/null +++ b/db/migrations_v3/20181110192012_create_messages_announcements.js @@ -0,0 +1,32 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('messages_announcements', function (table) { + table.increments('mid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('title').notNullable(); + table.text('content'); + table.integer('views').defaultTo(0); + + table.string('recipient',128).notNullable().defaultTo('noone') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if recipient is deleted, direct to the special "no-one" group + //TODO: for now, we support only 1 author (where as graphql schema indicates support for [Group] authors) + table.string('author',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete message if author is deleted + + /* + we cannot declare this column yet, as table 'message_events' is not yet created. + it will be declared in the migration for the 'message_events' table. + table.integer('for_event') + .references('mid').inTable('message_events') + .onDelete('SET NULL'); + */ + + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('messages_announcements'); +}; diff --git a/db/migrations_v3/20181110192431_create_messages_events.js b/db/migrations_v3/20181110192431_create_messages_events.js new file mode 100644 index 0000000000000000000000000000000000000000..35ed0fa6d1c2c61b65cd1be317c699b67f782a70 --- /dev/null +++ b/db/migrations_v3/20181110192431_create_messages_events.js @@ -0,0 +1,39 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('messages_events', function (table) { + table.increments('mid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('title').notNullable(); + table.text('content'); + table.string('location').notNullable(); + table.dateTime('start_time').notNullable(); + table.dateTime('end_time').notNullable(); //TODO: add a CHECK (https://www.postgresql.org/docs/current/ddl-constraints.html) that start_time < end_time + + table.string('recipient',128).notNullable().defaultTo('noone') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if recipient is deleted, direct to the special "no-one" group + //TODO: for now, we support only 1 author (where as graphql schema indicates support for [Group] authors) + table.string('author',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete message if author is deleted + + table.integer('for_announcement') + .references('mid').inTable('messages_announcements') + .onDelete('SET NULL'); + + //TODO: add support for participatingGroups and participatingUsers from the graphql schema + + }).then(() => { + //update 'announcements' table by adding the 'for_event' column + knex.schema.table('announcements', function (table) { + table.integer('for_event') + .references('mid').inTable('messages_events') + .onDelete('SET NULL'); + }); + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('messages_events'); +}; diff --git a/db/migrations_v3/20181110192917_create_messages_private_posts.js b/db/migrations_v3/20181110192917_create_messages_private_posts.js new file mode 100644 index 0000000000000000000000000000000000000000..c69bdb5d1af76ec1d796ee3771b13bbe787b4102 --- /dev/null +++ b/db/migrations_v3/20181110192917_create_messages_private_posts.js @@ -0,0 +1,19 @@ + +exports.up = function(knex, Promise) { + return knex.schema.createTable('messages_private_posts', function (table) { + table.increments('mid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('title').notNullable(); + table.text('content'); + + table.string('recipient',128).notNullable().defaultTo('noone') + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if recipient is deleted, also delete the private post (don't direct to noone to avoid spamming it, + supposed to be private) + table.string('author').notNullable(); //must refer to a User's uid. + }); +}; + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('messages_private_posts'); +}; diff --git a/db/migrations_v3/20181110193153_create_messages_questions.js b/db/migrations_v3/20181110193153_create_messages_questions.js new file mode 100644 index 0000000000000000000000000000000000000000..fb9e0e2e080e98102aa09d2932d59e5d455e15ac --- /dev/null +++ b/db/migrations_v3/20181110193153_create_messages_questions.js @@ -0,0 +1,27 @@ + +exports.up = function(knex, Promise) { + return knex.schema.createTable('messages_questions', function (table) { + table.increments('mid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('title').notNullable(); + table.text('content'); + + table.string('recipient',128).notNullable().defaultTo('noone') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if recipient is deleted, direct to the special "no-one" group + table.string('author').notNullable(); //must refer to a User's uid. + + /* + we cannot declare this column yet, as table 'answers' is not yet created. + it will be declared in the migration for the 'answers' childtable. + table.integer('for_answer') + .references('mid').inTable('answers') + .onDelete('SET NULL'); //if answer is deleted, set for_answer to null + */ + }); +}; + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('messages_questions'); +}; diff --git a/db/migrations_v3/20181110193226_create_messages_answers.js b/db/migrations_v3/20181110193226_create_messages_answers.js new file mode 100644 index 0000000000000000000000000000000000000000..9f8f71ea2b02397692bc72220b904a7b64bbbc91 --- /dev/null +++ b/db/migrations_v3/20181110193226_create_messages_answers.js @@ -0,0 +1,39 @@ + +exports.up = function(knex, Promise) { + return knex.schema.createTable('messages_answers', function (table) { + table.increments('mid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.string('title').notNullable(); + table.text('content'); + + table.string('recipient',128).notNullable().defaultTo('noone') + .references('gid').inTable('groups') + .onDelete('SET DEFAULT'); //if recipient is deleted, direct to the special "no-one" group + /* + we require that a GraphQL Answer's author field be the same as its recipient field. + since recipient is already known (defined in the 'messages' parent-table), + then we simply don't store answers' authors in database. + (another way would be to add equality constraints, but uselessly complex.) + table.string('author', 128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); + */ + + table.integer('for_question').notNullable() + .references('mid').inTable('messages_questions') + .onDelete('CASCADE'); //if question is deleted, also delete the answer + + }).then(() => { + //update 'questions' table by adding the 'for_answer' column + knex.schema.table('questions', function (table) { + table.integer('for_answer') + .references('mid').inTable('messages_answers') + .onDelete('SET NULL'); //if answer is deleted, set for_answer to null + }); + }); +}; + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('messages_answers'); +}; diff --git a/db/migrations_v3/20181110193517_create_requests_user_join_group.js b/db/migrations_v3/20181110193517_create_requests_user_join_group.js new file mode 100644 index 0000000000000000000000000000000000000000..b2c04664be9d138a5093299cccf31576a2d17cce --- /dev/null +++ b/db/migrations_v3/20181110193517_create_requests_user_join_group.js @@ -0,0 +1,17 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('requests_user_join_group', function (table) { + table.increments('rid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.text('request_comment'); + table.string('request_from').notNullable(); //must refer to a User's uid. + table.string('request_to',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete request from database if recipient group is deleted + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('requests_user_join_group'); +}; diff --git a/db/migrations_v3/20181110193612_create_requests_group_join_metagroup.js b/db/migrations_v3/20181110193612_create_requests_group_join_metagroup.js new file mode 100644 index 0000000000000000000000000000000000000000..9013fb3c2a9ba887e8966d92f443efd33f5c1feb --- /dev/null +++ b/db/migrations_v3/20181110193612_create_requests_group_join_metagroup.js @@ -0,0 +1,19 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('requests_group_join_metagroup', function (table) { + table.increments('rid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.text('request_comment'); + table.string('request_from',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if requesting group is deleted, also delete request + table.string('request_to',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete request from database if recipient group is deleted + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('requests_group_join_metagroup'); +}; diff --git a/db/migrations_v3/20181110193656_create_requests_group_coauthor_event.js b/db/migrations_v3/20181110193656_create_requests_group_coauthor_event.js new file mode 100644 index 0000000000000000000000000000000000000000..7b0e2e08e9369116719ef2ffc985a498477bbd93 --- /dev/null +++ b/db/migrations_v3/20181110193656_create_requests_group_coauthor_event.js @@ -0,0 +1,23 @@ + +exports.up = function (knex, Promise) { + return knex.schema.createTable('requests_group_coauthor_event', function (table) { + table.increments('rid'); //autoincrementing (non-nullable) primary key + table.timestamps(true, true); //adds timestamptz-type (a PostgresQL data type) updated_at and created_at columns + + table.text('request_comment'); + table.string('request_from',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //if requesting group is deleted, also delete request + table.string('request_to',128).notNullable() + .references('gid').inTable('groups') + .onDelete('CASCADE'); //delete request from database if recipient group is deleted + + table.integer('for_event').notNullable() + .references('mid').inTable('messages_events') + .onDelete('CASCADE'); //if event is deleted, also delete request + }); +}; + +exports.down = function (knex, Promise) { + return knex.schema.dropTable('requests_group_coauthor_event'); +}; diff --git a/db/seeds_v3/00_special_groups.js b/db/seeds_v3/00_special_groups.js new file mode 100644 index 0000000000000000000000000000000000000000..857108df027f1189f25315db7856c35d27f97785 --- /dev/null +++ b/db/seeds_v3/00_special_groups.js @@ -0,0 +1,38 @@ + +/** + * Noter que contrairement aux seed files suivants, ceci ne contient pas d'entrées de test + * mais des entrées qui doivent être mises dans la BDD même en prod... + */ + +exports.seed = async function (knex, Promise) { + // Deletes ALL existing entries + // first we have to delete all the messages, because deleting 'noone' would trigger their ON DELETE, which would require 'noone' to exist... + await knex('messages_announcements').del(); + await knex('messages_events').del(); + //await knex('messages_private_posts').del(); <-- ok this one is safe, because we set ON DELETE CASCADE (and not SET DEFAULT) + await knex('messages_questions').del(); + await knex('messages_answers').del(); + await knex('groups').del(); + // Inserts seed entries + const special_groups = [{ + name: 'Special root group', + gid: 'root', + description: 'root group, theoretically the ancestor of all SimpleGroups', + school: 'notaschool', + parent_gid: 'root', + type: 'simple' + }, { + name: "Special 'no-one' group", + gid: 'noone', + description: "'noone' group, direct child of 'root' group.\ + Serves as recipient of all unpublished Messages (e.g.drafts, messages with deleted recipient...)", + school: 'polytechnique', + parent_gid: 'root', + type: 'simple' + } + ]; + + return knex('groups').insert(special_groups) + .then(console.log("finished running 00_special_groups")); + +}; diff --git a/db/seeds_v3/01_dummy_groups.js b/db/seeds_v3/01_dummy_groups.js new file mode 100644 index 0000000000000000000000000000000000000000..cd68bad67286cf524cdda534acb2be579ddc427b --- /dev/null +++ b/db/seeds_v3/01_dummy_groups.js @@ -0,0 +1,133 @@ + +//TODO: this shows that the parent_gid REFERENCES groups(gid) constraint doesn't work!! +//(see entry gid='ask') + +exports.seed = async function(knex, Promise) { + // Deletes ALL existing entries + //await knex('groups').del(); //--> NO! don't delete root! + // Inserts seed entries + const simple_groups = [{ + name: 'BR', + gid: 'br', + website: 'br.binets.fr', + description: 'Le Binet Réseau est responsable du réseau internet des élèves sur le campus de l\'Ecole polytechnique.', + school: 'polytechnique', + parent_gid: 'kes', + type : 'simple' + },{ + name: 'JTX', + gid: 'jtx', + website: 'binet-jtx.com', + description: "Le Journal Télévisé de l'X te fait revivre les évènements promo en images et expose régulièrement ses créations.", + school: 'polytechnique', + parent_gid: 'kes', + type : 'simple' + },{ + name: 'Faërix', + gid: 'faerix', + website: 'faerix.net', + description: "Faërix est le binet de jeux de l'école : jeux de cartes, de plateau, de rôles... Le binet organise chaque année, sur un week-end, une convention de jeux de rôles, les Rencontres Rôlistes de l'X.", + school: 'polytechnique', + parent_gid: 'kes', + type : 'simple' + },{ + name: 'Bôbar', + gid: 'bobar', + website: 'http://tdb.bobar.pro/', + description: "Le bar de l'École polytechnique, tenu par et pour les X. Le BôBar t'enkhûle avec affection", + school: 'polytechnique', + parent_gid: 'kes', + type : 'simple' + },{ + name: 'Kès', + gid: 'kes', + website: 'kes.binets.fr', + description: "La Kès est le bureau des élèves de l'école, constitué de 16 kessiers élus par la promotion. Son local est ouvert de 12h30 à 22h30 tous les jours de la semaine (vendredi jusqu'à 18h15), et se trouve à l'entrée Est du Bataclan. Pour plus d'infos : consultez le Wikix ou téléphonez au 2726 pour faire un gros bisou à Mina et Zaza", + school: 'polytechnique', + parent_gid: 'root', + type : 'simple' + },{ + name: 'DaTA', + gid: 'data', + website: 'data-ensta.fr', + school: 'ensta', + parent_gid: 'bdeensta', + type : 'simple' + },{ + name: 'Laser Wave', + gid: 'laserwave', + website: 'laserwave.fr', + school: 'supoptique', + type : 'simple' + },{ + name: 'WikiX', + gid: 'wikix', + website: 'https://wikix.polytechnique.org/', + school: "polytechnique", + description: "Le recueil de la mémoire des élèves", + type: "simple", + },{ + name: 'BDE Ensta', + gid: 'bdeensta', + website: 'http://bde.ensta-paristech.fr/', + school: 'ensta', + type : 'simple' + },{ + name: "X-Chine", + gid: "x-chine", + school: "polytechnique", + description: `X-Chine est le binet qui rassemble les amoureux et les curieux de l'Empire du Milieu, qu'ils soient chinois ou non !`, + type: "simple" + },{ + name: 'Subaïsse', + gid: 'subaisse', + description: 'Le Binet de ceux qui subissent', + school: 'polytechnique', + parent_gid: 'kes', + type : 'simple' + },{ + name: 'X-Broadway', + gid: 'xbroadway', + website: 'https://xbroadway.binets.fr/', + description: "Créé par la promotion 2012, le binet X-Broadway prépare pendant une année une comédie musicale qu'il joue durant le tronc commun de la promo suivante. Chaque année, le bureau est composé de multiples pôles : comédie, danse, manute, musique, et prez'. Une fois le bureau constitué, il recrute sa troupe parmi les étudiants du plateau de Saclay.", + school: "polytechnique", + parent_gid: 'kes', + type: 'simple' + },{ + name: 'Œnologie', + gid: 'oenologie', + website: 'oenologie.binets.fr', + description: "Le binet de la Finesse et du Bon Goût par excellence. Cours d'initiation, dégustations, soirées Vins & Fromages, voyages chez les producteurs : le Binet Œnologie est là pour partager la passion du vin avec tous, néophytes ou connaisseurs.", + school: "polytechnique", + parent_gid: "kes", + type: "simple" + },{ + name: "Tribunes de l'X", + gid: 'politix', + website: 'https://www.facebook.com/Tribunes-de-lX-250533552110256/', + description: "Le binet Tribunes de l'X a pour but d'intéresser les élèves à la \"vie de la cité\" en organisant des conférences ou débats, autour de grandes sujets d'actualité et d'enjeux fondamentaux pour le débat public, avec des personnalités diverses, hommes politiques ou intellectuels. Cette association est la tribune étudiante de l'X.", + school: "polytechnique", + parent_gid: "kes", + type: 'simple' + },{ + name: "X-Finance", + gid: 'x-finance', + description: "X Finance gère un fonds d'investissement étudiant au profit de la FX en partenariat avec Edmond de Rothschild. Nous développons également des modèles algorithmiques pour gérer le risque. Le binet organise également des événements de networking en partenariat avec des écoles françaises et européeennes.", + school: "polytechnique", + parent_gid: "kes", + type: "simple" + },{ + name: "ASK", + gid: "ask", + description: "L'Action Sociale de la Kès regroupe les activités à caractère social et solidaire du platâl, comme le soutien scolaire, la bibliothèque de rue, les maraudes, le don du sang, les journées handicap ou encore le parrainage d'enfants. Chacun y trouvera son compte et pourra s'y investir à sa guise! Si tu as l'âme de l'Abbé Pierre ou le sex appeal d'Adriana Karembeu, chauffe toi et viens donner un peu de ton temps aux autres! Promis, on est gentils. Des bisous bisous!", + school: "polytechnique", + parent_gid: "kes", //TODO: WTF? this shouldn't work!! + type: "simple" + } + + ]; + + return knex('groups').insert(simple_groups) + .then(console.log("finished running 01_dummy_groups")); + +}; diff --git a/db/seeds_v3/02_dummy_metagroups.js b/db/seeds_v3/02_dummy_metagroups.js new file mode 100644 index 0000000000000000000000000000000000000000..b28c9c3c9cc72199b9019e08840cd85293bd1b9a --- /dev/null +++ b/db/seeds_v3/02_dummy_metagroups.js @@ -0,0 +1,26 @@ + +exports.seed = async function (knex, Promise) { + // Deletes ALL existing entries + //await knex('groups').del(); --> don't do that! + // Inserts seed entries + const meta_groups = [{ + name: 'Fédérez', + gid: 'federez', + website: 'federez.io', + description: "L'association de toutes les associations de réseau des écoles", + school: 'notaschool', + type: 'meta' + }, + { + name: 'BSCkBl', + gid: 'bsckbl', + website: 'bsckbl.binets.fr', + school: 'notaschool', + type: 'meta' + } + + ]; + + return knex('groups').insert(meta_groups) + .then(console.log("finished running 02_dummy_metagroups")); +}; diff --git a/db/seeds_v3/03_dummy_metagroup_memberships.js b/db/seeds_v3/03_dummy_metagroup_memberships.js new file mode 100644 index 0000000000000000000000000000000000000000..7de8b131157c125cf4e6196ce782b2a40474bb22 --- /dev/null +++ b/db/seeds_v3/03_dummy_metagroup_memberships.js @@ -0,0 +1,24 @@ + +exports.seed = async function(knex, Promise) { + // Deletes ALL existing entries + await knex('metagroup_memberships').del(); + // Inserts seed entries + const metagroup_memberships = [{ + simple_group_gid: "br", + meta_group_gid: "federez", + rights : "member" + }, + { + simple_group_gid : "data", + meta_group_gid : "federez", + rights : "admin" + }, + { + simple_group_gid : "bobar", + meta_group_gid : "bsckbl", + rights : "admin" + } + ]; + return knex('metagroup_memberships').insert(metagroup_memberships) + .then(console.log("finished running 03_dummy_metagroup_memberships")); +}; diff --git a/db/seeds_v3/04_dummy_announcements.js b/db/seeds_v3/04_dummy_announcements.js new file mode 100644 index 0000000000000000000000000000000000000000..66d5a412a9cc3e2fa2414f06258f462411465835 --- /dev/null +++ b/db/seeds_v3/04_dummy_announcements.js @@ -0,0 +1,40 @@ + +exports.seed = async function (knex, Promise) { + // Deletes ALL existing entries + await knex('messages_announcements').del(); + // Inserts seed entries + const announcements = [{ + mid: 0, + title: "Fissurer c'est bien", + content: "Les nouveaux ordis du JTX sont arrivés ! Le BR aide à les installer ;)", + //authors: ['br'] + author: 'br' + },{ + mid: 1, + title: "Proj'et Promotion", + content: "La nouvelle proj' du JTX arrive !", + //authors: ['jtx'] + author: 'jtx' + },{ + mid: 2, + title: "Fête de la Lune", + content: "C'est bientôt la fête de la Lune ! Inscrivez-vous pour un dîner-spectacle dans le Grand Hall !", + //authors: ['x-chine'] + author: 'x-chine' + },{ + mid: 3, + title: "Formation Web", + content: "Envie d'apprendre à faire un site Web en Django ? Alors viens en amphi Sauvy ce jeudi à 20h !", + //authors: ['br'] + author: 'br' + },{ + mid: 4, + title: "Journées FedeRez", + content: "Cette année, nous parlerons de vie privée, protection des données et sécurité", + //authors: ['federez'] + author: 'federez' + } + ]; + return knex('messages_announcements').insert(announcements) + .then(console.log("finished running 04_dummy_announcements")); +}; diff --git a/db/seeds_v3/05_dummy_events.js b/db/seeds_v3/05_dummy_events.js new file mode 100644 index 0000000000000000000000000000000000000000..69bc78b5223dbb4df1374f08b70b3af9dc16c138 --- /dev/null +++ b/db/seeds_v3/05_dummy_events.js @@ -0,0 +1,42 @@ + +exports.seed = async function(knex, Promise) { + // Deletes ALL existing entries + await knex('messages_events').del(); + // Inserts seed entries + const events = [{ + title : "Fête de la lune", + content : "La fête de la lune, c'est bientôt dans le grand hall !", + location: "Grand Hall", + start_time : knex.fn.now(), + end_time: knex.fn.now(), + //authors: ['x-chine'] + author: 'x-chine' + },{ + title: "Perm BR du mardi soir", + content: "La perm' BR c'est maintenant!", + location: "Amphi Sauvy", + start_time: knex.fn.now(), + end_time: knex.fn.now(), + //authors: ['br'] + author: 'br' + },{ + title: "Formation Git", + content: "Aujourd'hui, on va parler du système de contrôle de versions Git, qui est particulièrement utile pour travailler à plusieurs sur des projets informatiques: PSC, code de PI ou de projet de MAP, site binet, quoi que ce soit!", + location: "Amphi Painlevé", + start_time: knex.fn.now(), + end_time: knex.fn.now(), + //authors: ['br'] + author: 'br' + },{ + title: "Formation Web", + content: "Envie d'apprendre à faire un site Web en Django ? Alors viens en amphi Sauvy ce jeudi à 20h !", + location: "Amphi Painlevé", + start_time: knex.fn.now(), + end_time: knex.fn.now(), + //authors: ['br'] + author: 'br' + } + ]; + return knex('messages_events').insert(events) + .then(console.log("finished running 05_dummy_events")); +}; diff --git a/db/seeds_v3/06_dummy_private_posts.js b/db/seeds_v3/06_dummy_private_posts.js new file mode 100644 index 0000000000000000000000000000000000000000..a25a7cce0afb7f79efd041c3571b451cb5324be2 --- /dev/null +++ b/db/seeds_v3/06_dummy_private_posts.js @@ -0,0 +1,20 @@ + +exports.seed = async function (knex, Promise) { + // Deletes ALL existing entries + await knex('messages_private_posts').del(); + // Inserts seed entries + const private_posts = [{ + title: "Anatole met du temps à faire avancer resolver_dev", + content: "C'est scandaleux qu'anatole mette autant de temps à corriger les bugs et merge sa branche. Comment on fait pour présenter le projet aux 17 ?", + author: "guillaume.wang", + }, + { + title: "Sushi tradi", + content: "Le prochain sushi tradi aura lieu ce mardi. Tout le monde est invité.", + author: "martin.guillot", + }, + ]; + + return knex('messages_private_posts').insert(private_posts) + .then(console.log("finished running 06_dummy_private_posts")); +}; diff --git a/db/seeds_v3/07_dummy_requests.js b/db/seeds_v3/07_dummy_requests.js new file mode 100644 index 0000000000000000000000000000000000000000..ae99368ed8450965b030ebda525c91bfc584e365 --- /dev/null +++ b/db/seeds_v3/07_dummy_requests.js @@ -0,0 +1,45 @@ + +exports.seed = async function(knex, Promise) { + // Deletes ALL existing entries + await knex('requests_user_join_group').del(); + await knex('requests_group_join_metagroup').del(); + await knex('requests_group_coauthor_event').del(); + // Inserts seed entries + const user_join_group_reqs = [{ + rid: 1, + request_to: 'br', + request_comment: "C'est ici pour développer sigma ?", + request_from: "anatole.romon" + }, + { + rid: 2, + request_to: 'br', + request_comment: "Bonjour, je cherche le binet subaisse", + request_from: "quentin.gendre" + }, + { + rid: 3, + request_to: 'jtx', + request_comment: "Quand je serais grand je serais cinéaste !", + request_from: "anatole.romon" + } + ]; + await knex('requests_user_join_group').insert(user_join_group_reqs); + + /* + const group_coauthor_event_reqs = [{ + rid: 4, + request_to: "br", + request_comment: "nous aussi on veut coder sigma", + for_event: 42, //TODO: ca ne va pas marcher ca... + request_from: "subaisse" + } + ]; + console.log("on insere une group_coauthor_event request avec for_event=42. ca va crasher..."); + await knex('requests_group_coauthor_event').insert(group_coauthor_event_reqs); + */ + + console.log("finished running 07_dummy_requests"); + return; + +}; diff --git a/memo knexjs.md b/memo knexjs.md new file mode 100644 index 0000000000000000000000000000000000000000..fc4afc5b311a5f558b3512d56e0c75414580fff3 --- /dev/null +++ b/memo knexjs.md @@ -0,0 +1,59 @@ +Mémo d'introduction à Knex.js pour les nuls +=== + +```info +Note : ce mémo a été rédigé à l'origine pour le CONTRIBUTING.md de shitpost-backend, donc fait parfois référence à des fichiers de ce projet. C'est indiqué à chaque fois lorsque c'est le cas. +``` + +J'utilise Knex.js avec PostgreSQL (comme dans sigma). + +## knexfile.js et knex_init.js + +`knexfile.js`: +- Fichier utilisé par les command-line-tools de knex (`knex migrate:*` et `knex seed:*`) +- Le rôle principal du fichier est de préciser (quelle db on utilise, et) où stocker les fichiers de migrations Knex ainsi que les _seeds_. +- Malheureusement, la documentation de knex est très pauvre sur quoi mettre dans knexfile.js... [2018-10-13] + - https://github.com/tgriesser/knex/issues/563 + - https://github.com/tgriesser/knex/issues/1223 +- Destiné aux migrations et aux seeds, pas aux fichiers js. + - Correspond à cette partie de la doc : https://knexjs.org/#knexfile (Migrations/CLI/knexfile) +- Pour utiliser les commandes `knex` en CLI, il faut se placer dans le directory qui contient ce fichier. + +`knex_init.js`: +- Configure et exporte un objet knex permettant les requêtes SQL +- Spécifie la bdd à laquelle se connecter, son adresse, en tant que quel user (en l'occurrence quel Role) se connecter au serveur, et son mdp. +- Contrairement à `knexfile.js`, il est destiné au serveur Nodejs (i.e. aux fichiers js). + - Correspond à cette partie : https://knexjs.org/#Installation-client ("Initializing the library") + +Il est évident que ces deux fichiers ont en gros le même contenu ! C'est un "problème" [connu](https://github.com/tgriesser/knex/issues/1223), et actuellement (2018-10, knexjs v0.15.2) c'est bien comme ça qu'on est censé faire. (Mais en fait ce n'est pas un gros problème, juste un peu confusing, et engendre un peu de redondance.) + +## "Migrations" et "Seeds" +https://gist.github.com/NigelEarle/70db130cc040cc2868555b29a0278261 + +### Migrations +> Migrations are a way to make database changes or updates, like creating or dropping tables, as well as updating a table with new columns with constraints, *via generated scripts*. We can build these scripts via the command line using `knex` command line tool. + +En gros, les migrations permettent de définir, de façon "statique" et donc plus lisible, le schéma de la BDD. +Autre cas de figure : elles permettent de définir de façon statique les modifications à apporter au schéma d'une BDD existante. C'est moins utile pour nous, mais sert à apporter des modifications à la BDD d'une application déjà en production, en s'assurant que si ça casse quelque chose on aura la possibilité de revenir en arrière. + +Elles consistent en une liste de "commandes knex" qui disent exactement quoi faire. C'est le même principe que quand on exporte une BDD SQL : le fichier obtenu est en fait une liste de commandes SQL, qui lorsqu'executée, recrée la BDD exportée. +Sauf qu'en plus, il y a aussi une liste de "commandes knex" qui disent quoi faire pour *défaire la migration*, c'est le `exports.down` (alors que la liste disant quoi faire pour faire la migration est dans `exports.up`). + +### Seeds +> Similar to migrations, the `knex` module allows us to *create scripts, called seed files, to insert initial data into our tables*! + +Les seeds permettent d'insérer des données dans la BDD. Bien sûr, il est beaucoup plus logique de définir (`knex migrate:make create_blablabla`) et d'exécuter (`knex migrate:latest`) ses migrations, avant de gérer les seeds. + +### Choix du schéma et rédaction des migrations +**[spécifique à shitpost :]** +On a défini les migrations (`./db/migrations/*`) en se basant sur le schéma GraphQL (`typeDefs.js`) (et pas l'inverse), ce qui se voit bien puisqu'on suit presque exactement ce dernier. + +**[spécifique à shitpost :]** +Un point particulier cependant : comme expliqué en commentaires du migration-file `./db/migrations/[timestamp]_create_comments.js`, on n'a pas déclaré le champ "forMessage" de la table "comments" comme ayant "messages.id" comme foreign key, car ce n'est pas possible avec PostgreSQL v10. + +### Un petit problème avec les seeds +Puisqu'à chaque `knex seed:run` on DELETE toutes les entrées de toutes les tables avant de les reconstruire (cf. ligne 3 de tous les seedfiles : "`// Deletes ALL existing entries. return knex('table_name').del().then(...)`"), on tombe sur ce genre de problème : +``` +error: update or delete on table "channels" violates foreign key constraint "messages_channel_foreign" on table "messages" +``` +Solution : spécifier dans le schéma (fichiers migrations) ce qu'il faut faire quand l'objet référencé par une colonne FOREIGN KEY est détruit, avec l'option ON DELETE (reflété par `.onDelete(...)` de knex). https://www.postgresql.org/docs/current/static/ddl-constraints.html#DDL-CONSTRAINTS-FK diff --git a/memo postgresql.md b/memo postgresql.md index 44855f26fd30a41bbdacd0f0d5b4e2fb6b888b43..4c0863a7622797e0f37d692158bdb4db556850f5 100644 --- a/memo postgresql.md +++ b/memo postgresql.md @@ -63,7 +63,7 @@ Pour se connecter à un terminal "psql" en tant que le *rôle* "postgres", plusi On choisit le mode d'authentification de psql en modifiant `pg_hba.conf` [(voir solution ici)](https://stackoverflow.com/questions/14588212/resetting-password-of-postgresql-on-ubuntu). Quelques petites remarques : -- sur MD5 : cette méthode de hashage est complètement cassée (en 2018-10) donc l'utiliser n'est pas du tout sécurisé ! A ne pas (plus) utiliser dans des applis en production ! +- sur MD5 : comme vous le savez, le hashage MD5 est complètement craqué (en 2018-10), donc il ne faut surtout pas l'utiliser pour stocker des mdp encryptés -- mais pas d'inquiétude, ce n'est pas le cas. Ce que signifie cette méthode est que le mdp est hashé avant d'être envoyé du client vers le serveur. C'est utile lorsque le serveur est sur une machine distante, car alors le mdp passe sur le réseau et risque d'être sniffé. - sur "peer authentication" : dans ce scénario, c'est votre système d'exploitation (OS) qui garantit votre authentification auprès du serveur PostgreSQL. Bien sûr, ceci ne marche que si le serveur peut faire confiance à votre OS, donc que si vous utilisez un serveur stocké sur votre propre machine. - pour plus d'information, voir la [section "authentification" de la doc](https://www.postgresql.org/docs/current/static/auth-methods.html). @@ -77,7 +77,7 @@ john@pc-de-john:~$ psql ma_bdd # se connecter à la BDD "ma_bdd" en tant que le rôle PostgreSQL "john" ``` -- Se connecter en tant qu'une *rôle* différent de son identité en tant qu'*utilisateur Unix* : +- Se connecter en tant qu'un *rôle* différent de son identité en tant qu'*utilisateur Unix* : ```bash john@pc-de-john:~$ psql ma_bdd -U postgres # -U: se connecter à la BDD "ma_bdd" en tant que le rôle PostgreSQL "postgres" @@ -106,6 +106,8 @@ sudo: unable to initialize policy plugin john@pc-de-john:~$ psql -U pika # pas de BDD spécifiée: se connecter à la BDD "pika" en tant que le rôle PostgreSQL "pika" psql: FATAL: database "pika" does not exist +john@pc-de-john:~$ psql ma_bdd -U pika +# se connecter à ma_bdd en tant que "pika" ``` ``` @@ -124,36 +126,44 @@ Pour sortir de psql, entrer `\q`. (^D marche aussi.) Commandes utiles de psql (voir aussi ce [cheat sheet postgresql](https://gist.github.com/Kartones/dd3ff5ec5ea238d4c546) pas trop mal) (sinon il y en a plein d'autres sur internet). ) : - `\q`: Quit/Exit -- `\c __database__`: Connect to a database -- `\d __table__`: Show table definition including triggers -- `\dt *.*`: List tables from all schemas (if `*.*` is omitted will only show SEARCH_PATH ones) - `\l`: List databases -- `\dn`: List schemas +- `\c __database__`: Connect to a database +- `\d __table__`: Show table definition including triggers, i.e. describes table (Use `\d+` to show even more stuff) +- `\dt *.*`: List tables in current database (`\dt *.*` to list tables from all schemas) +- `\dn`: List schemas (je recommande ne pas y toucher -- voir section suivante) - `\x`: Pretty-format query results instead of the not-so-useful ASCII tables User Related: - `\du`: List users - `\du __username__`: List a username if present. +##### "Schémas PostgreSQL" + +Le/la lecteur(rice) attentif(ve) aura remarqué que certaines de ces commandes parlent de schemas. C'est une fonctionnalité avancée à laquelle je recommande de ne pas toucher du tout, à notre niveau. Pour les curieux, voir les explications de [la doc](https://www.postgresql.org/docs/current/ddl-schemas.html).) + ### Créer un nouvel rôle Il est utile de s'habituer à travailler "en tant que" un utilisateur (en l'ocurrence un rôle) qui n'a pas tous les droits sur tout, parce que c'est ce qui se passe dans la vraie vie. - https://www.postgresql.org/docs/current/static/database-roles.html - https://www.postgresql.org/docs/current/static/role-attributes.html -Il est donc recommandé de faire soit un `createuser` (la commande bash, avec les options qui vont bien), soit un `CREATE ROLE rolename LOGIN PASSWORD 'rolepw';` dans un terminal psql, avant de créer des BDDs. Bien penser à donner les permissions idoines à ce nouveau rôle (typiquement, se connecter au serveur avec un mot de passe, créer des bases de données...). +Il est donc recommandé de faire soit un `createuser` [dans un terminal bash](https://www.postgresql.org/docs/current/app-createuser.html), soit un `CREATE ROLE rolename WITH LOGIN PASSWORD 'rolepw';` [dans un terminal psql](https://www.postgresql.org/docs/current/sql-createrole.html), avant de créer des BDDs. Bien penser à donner les permissions idoines à ce nouveau rôle (typiquement, se connecter au serveur avec un mot de passe : "LOGIN" et "PASSWORD", créer des bases de données : "CREATEDB"...). Si on a oublié, on peut toujours faire `ALTER ROLE rolename WITH ...`. Une fois un nouveau rôle créé, il suffit de faire comme quand on est connecté en tant que postgres. Petite subtilité : à moins que vous vouliez travailler avec un rôle qui matche exactement votre identité en tant qu'utilisateur Unix, il ne faut pas utiliser le mode d'authentification "peer". Il faut donc potentiellement modifier `pg_hba.conf` pour spécifier au serveur PostgreSQL quel sera le mode d'authentification utilisé. -## La suite +### Créer une nouvelle base de données + +Comme pour créer un nouveau rôle, deux possibilités : `createdb` [dans un terminal bash](https://www.postgresql.org/docs/current/app-createdb.html), ou `CREATE DATABASE db_name` [dans un terminal psql](https://www.postgresql.org/docs/current/sql-createdatabase.html). Il est possible, mais pas nécessaire, de rajouter des options, qui seront modifiables après la création avec `ALTER DATBASE`. + +## En savoir plus sur PostgreSQL Vous êtes prêt à lire le chapitre 1 de la documentation : https://www.postgresql.org/docs/current/static/tutorial-arch.html A noter qu'en fait, seul le chapitre 1 (Tutorial) est vraiment à lire. Les autres chapitres sont présents pour référence (c'est une documentation après tout). -[Table des matières](https://www.postgresql.org/docs/10/static/index.html) de la documentation: +[Table des matières](https://www.postgresql.org/docs/current/static/index.html) de la documentation: > Preface > I. Tutorial > 1. Getting Started @@ -171,8 +181,25 @@ A noter qu'en fait, seul le chapitre 1 (Tutorial) est vraiment à lire. Les autr ## Et si on travaille sur une BDD distante ? -Exactement le même principe, sauf qu'on n'a plus la possibilité de se connecter en tant que (rôle de) l'administrateur principal postgres. On doit travailler en tant qu'un rôle spécifique au projet concerné (ce qu'il est recommandé de faire même en travaillant en local d'ailleurs). +Exactement le même principe, sauf qu'on n'a plus la possibilité d'être connecté en tant que (rôle de) l'administrateur principal postgres. On doit travailler en tant qu'un rôle spécifique au projet concerné (ce qu'il est recommandé de faire même en travaillant en local d'ailleurs). + +De plus, le choix du mode d'authentification (pg_hba.conf, sur le serveur distant) *doit* être par mot de passe. + +A noter que, pour le BR, il sera très rare de travailler sur une BDD distante. En effet, +- pour les applications web, elles sont le plus souvent hébergées sur le même serveur où est hébergé le serveur PostgreSQL. +- pour administrer une BDD, le BRman se connecte en ssh au serveur pour faire ses manips, donc les requêtes PostgresQL sont effectuées à partir de sa session i.e. déjà sur le serveur. + +## Et maintenant ? + +Vous savez à présent comment créer une base de données PostgreSQL et faire les manips de base. Vous devriez avoir, ou savoir comment créer, un utilisateur `sigma_dev` et une bdd *vide* `sigma` (si vous travaillez sur le projet sigma par exemple). + +Maintenant il faut mettre des choses dans cette bdd... L'option hardcore/débile consiste à tout faire à la main par des commandes SQL dans le terminal psql : créer des tables, et spécifier les colonnes qu'elles doivent avoir, et spécifier les paramètres des colonnes (not nullable, default, autoincrement...), puis INSERT des entrées aussi à la main. -De plus, le choix du mode d'authentification (pg_hba.conf, sur le serveur distant) doit être par mot de passe. +Certains outils permettent de le faire de façon intelligente : ils permettent +- de spécifier, dans un fichier, les tables (et leurs colonnes, et leurs paramètres) que vous souhaitez. Ce fichier s'appelle "fichier migration". On dit qu'on choisit le "schéma de la base de données". +- de spécifier, dans un autre fichier, des entrées initiales pour votre bdd. Ce fichier s'appelle "fichier seed". +Par exemple, c'est ce que permet de faire Knex.js, utilisé par le projet sigma notamment. +Mais ces deux notions sont plus générales. Par exemple, Symfony (le framework PHP) sait construire des fichiers migrations (i.e. choisir le schéma de votre bdd) automatiquement, en lisant les paramètres spécifiés dans les annotations de vos Entity.php. Django fait aussi un truc similaire. +Un memo Knex.js est disponible... euh... quelque part, il devrait s'appeler "memo knexjs". diff --git a/src/graphql/typeDefs/actions_wish_list.graphql b/src/graphql/typeDefs/actions_wish_list.graphql index c3ec72e10f724f2f750aca9afb48b2d7df9a7371..4fc70a09f220bc25a1201df8d8e60ce49772039d 100644 --- a/src/graphql/typeDefs/actions_wish_list.graphql +++ b/src/graphql/typeDefs/actions_wish_list.graphql @@ -14,12 +14,12 @@ type Query { 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 + message(mid:ID!): Message + announcement(mid:ID!): Announcement + event(mid:ID!): Event + privatePost(mid:ID!): PrivatePost + question(mid:ID!): Question + answer(mid:ID!): Answer # Request queries de base request(rid:ID!): Request @@ -111,6 +111,13 @@ type Mutation { - (le destinataire est un Group pour tous les Messages) """ + # Groups-independent mutations + editProfile( + nickname: String, + mail: String, + phone: String + ): User + # Viewer mutations likeGroup(groupId: ID!): Boolean # devenir sympathisant userParticipate( diff --git a/src/graphql/typeDefs/objects_wish_list.graphql b/src/graphql/typeDefs/objects_wish_list.graphql index a9630590b6ba5ad1f3bdf9afb6bd2fe64709556e..cd6fd9588c639011d92b7e30136a5f81c8031667 100644 --- a/src/graphql/typeDefs/objects_wish_list.graphql +++ b/src/graphql/typeDefs/objects_wish_list.graphql @@ -12,7 +12,7 @@ Conseils généraux pour toute tentative d'amélioration du schéma : - respecter la convention : - uid = user id - gid = group id - - id = message id + - mid = message id - rid = request id - choisir des noms clairs, précis, concis. - commenter lorsque c'est pertinent, également en étant clair, précis, concis. @@ -33,6 +33,7 @@ type User { nationality: String birthdate: String! promotion: String + #photo: TODO (difficile car nécessite de gérer des données non-texte) # Pour le contacter mail: String @@ -127,36 +128,35 @@ type MetaGroup implements Group { } union AuthorUnion = Group | [Group] | User -union RecipientUnion = Group | [Group] """ L'interface Message représente toute information que veut communiquer un groupe ou un user. -Par choix de paradigme, tout Message est adressé à un (ou des) groupe(s). +Par choix de paradigme, tout Message est adressé à un groupe (et un seul). Les types implémentés sont divisés en deux : - les Message émanant d'un groupe : Announcement et Event, ainsi que Answer - les Message émanant d'un user : PrivatePost, ainsi que Question """ interface Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! authors: AuthorUnion - recipients: RecipientUnion # destinataire du Message. forcement (un ou plusieurs) Group + recipient: Group } # Annonce effectuée par un ou plusieurs groupes. type Announcement implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! authors: [Group] - recipients: [Group] + recipient: Group importance: Int # importance de cette Announcement, sur une échelle de [??] à [??] (TODO) views: Int # nombre de vues @@ -167,14 +167,14 @@ type Announcement implements Message { # Événements organisés par un ou plusieurs groupes. type Event implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! authors: [Group] # Organisateurs de l'événement - recipients: [Group] + recipient: Group location: String! startTime: String! @@ -190,26 +190,26 @@ type Event implements Message { # Post interne d'un membre sur la page interne de son groupe type PrivatePost implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! authors: User - recipients: Group + recipient: Group } # Question posée par un user à un groupe type Question implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! authors: User - recipients: Group + recipient: Group # Référence la réponse donnée par le groupe à cette Question. Si pas encore répondu, null. forAnswer: Answer @@ -217,14 +217,14 @@ type Question implements Message { # Réponse à une Question type Answer implements Message { - id: ID! + mid: ID! createdAt: String! updatedAt: String! title: String! content: String! authors: Group - recipients: Group # doit être le même que authors + recipient: Group # doit être le même que authors # La question à laquelle cette Answer répond. Non-nullable bien sûr forQuestion: Question! @@ -238,12 +238,6 @@ Seuls les admins d'un Group (qu'il soit Simple ou Meta) ont le droit de valider 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