From 4f28a59c7ee28d5ea70a3745a697b5c335f33f7a Mon Sep 17 00:00:00 2001 From: julien Date: Mon, 2 Nov 2026 19:32:48 +0100 Subject: [PATCH] Imrpove migrations --- README.md | 34 +++++-- bin/initialize-admin | 10 ++ db/migrations/001_create_rodauth_tables.rb | 55 ++++++++++- db/migrations/002_create_members.rb | 2 + db/migrations/003_add_admin_to_members.rb | 13 --- db/migrations/003_create_memberships.rb | 56 +++++++++++ doc/statutes.md | 105 ++++++++------------- 7 files changed, 187 insertions(+), 88 deletions(-) delete mode 100644 db/migrations/003_add_admin_to_members.rb create mode 100644 db/migrations/003_create_memberships.rb diff --git a/README.md b/README.md index 0ce757e..73be58b 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,11 @@ ## À faire - limiter le spam sur `/contact/send` -- développer les comptes utilisateurs et leur interface -- afficher les recettes et dépenses sur la page d'accueil +- développer l'inscription des utilisateurs et la vérification des adresses électroniques +- développer les demandes d'adhésion et leur examen par le Noyau +- développer l'espace personnel des adhérents +- afficher les recettes et les dépenses sur la page d'accueil +- gérer les accès aux services selon la situation des adhérents ## Développement @@ -24,12 +27,22 @@ SMTP_PASSWORD=... Générer `SESSION_SECRET` avec `openssl rand -hex 64`. Ne pas versionner le fichier `.env`. -Appliquer les migrations : +Créer ou mettre à jour la base de données : ```sh bundle exec ruby bin/migrate ``` +Lors de la première installation, créer le compte du premier administrateur : + +```sh +bundle exec ruby bin/initialize-admin +``` + +Cette commande est à exécuter une seule fois. Elle crée un compte vérifié et +lui attribue les droits techniques d'administration. Elle ne crée pas de +demande d'adhésion. + Démarrer le serveur de développement : ```sh @@ -44,12 +57,19 @@ bundle exec irb -r dotenv/load -r ./netig.rb ## Production -Docker Compose fournit les variables d'environnement via `env_file` -et définit `RACK_ENV=production`. +Docker Compose fournit les variables d'environnement via `env_file` et définit +`RACK_ENV=production`. -Exécuter les migrations dans le conteneur applicatif avant de -démarrer la nouvelle version : +Exécuter les migrations dans le conteneur applicatif avant de démarrer la +nouvelle version : ```sh bundle exec ruby bin/migrate ``` + +Lors de la première installation, créer le premier administrateur depuis le +conteneur applicatif : + +```sh +bundle exec ruby bin/initialize-admin +``` diff --git a/bin/initialize-admin b/bin/initialize-admin index f14aa92..37c5781 100644 --- a/bin/initialize-admin +++ b/bin/initialize-admin @@ -42,6 +42,12 @@ abort "Opération annulée." unless %w[o oui].include?( console.gets&.strip&.downcase ) +verified_status_id = DB[:account_statuses] + .where(name: "Verified") + .get(:id) + +abort "Statut Verified introuvable." unless verified_status_id + auth = Rodauth.lib do db DB enable :create_account @@ -57,6 +63,10 @@ DB.transaction do account_id = auth.account_id_for_login(login: email) + DB[:accounts] + .where(id: account_id) + .update(status_id: verified_status_id) + DB[:members].insert( account_id: account_id, kind: "individual", diff --git a/db/migrations/001_create_rodauth_tables.rb b/db/migrations/001_create_rodauth_tables.rb index 2e4a2ec..3abe853 100644 --- a/db/migrations/001_create_rodauth_tables.rb +++ b/db/migrations/001_create_rodauth_tables.rb @@ -1,19 +1,68 @@ Sequel.migration do up do + create_table(:account_statuses) do + Integer :id, primary_key: true + String :name, null: false, unique: true + end + + from(:account_statuses).import( + [:id, :name], + [ + [1, "Unverified"], + [2, "Verified"], + [3, "Closed"] + ] + ) + + db = self + create_table(:accounts) do primary_key :id, type: :Bignum + + foreign_key :status_id, :account_statuses, + null: false, + default: 1 + String :email, null: false - index :email, unique: true + if db.supports_partial_indexes? + index :email, unique: true, where: { status_id: [1, 2] } + else + index :email, unique: true + end end create_table(:account_password_hashes) do - foreign_key :id, :accounts, primary_key: true, type: :Bignum + foreign_key :id, :accounts, + primary_key: true, + type: :Bignum + String :password_hash, null: false end + + create_table(:account_verification_keys) do + foreign_key :id, :accounts, + primary_key: true, + type: :Bignum + + String :key, null: false + + DateTime :requested_at, + null: false, + default: Sequel::CURRENT_TIMESTAMP + + DateTime :email_last_sent, + null: false, + default: Sequel::CURRENT_TIMESTAMP + end end down do - drop_table(:account_password_hashes, :accounts) + drop_table( + :account_verification_keys, + :account_password_hashes, + :accounts, + :account_statuses + ) end end diff --git a/db/migrations/002_create_members.rb b/db/migrations/002_create_members.rb index 77a27c8..df00c99 100644 --- a/db/migrations/002_create_members.rb +++ b/db/migrations/002_create_members.rb @@ -12,6 +12,8 @@ Sequel.migration do String :last_name String :organization_name + TrueClass :admin, null: false, default: false + index :account_id, unique: true constraint :members_kind_valid, diff --git a/db/migrations/003_add_admin_to_members.rb b/db/migrations/003_add_admin_to_members.rb deleted file mode 100644 index 4d58e86..0000000 --- a/db/migrations/003_add_admin_to_members.rb +++ /dev/null @@ -1,13 +0,0 @@ -Sequel.migration do - up do - alter_table(:members) do - add_column :admin, TrueClass, null: false, default: false - end - end - - down do - alter_table(:members) do - drop_column :admin - end - end -end diff --git a/db/migrations/003_create_memberships.rb b/db/migrations/003_create_memberships.rb new file mode 100644 index 0000000..09a338b --- /dev/null +++ b/db/migrations/003_create_memberships.rb @@ -0,0 +1,56 @@ +Sequel.migration do + up do + create_table(:memberships) do + primary_key :id, type: :Bignum + + foreign_key :member_id, :members, + type: :Bignum, + null: false + + String :status, null: false, default: "pending" + + DateTime :requested_at, + null: false, + default: Sequel::CURRENT_TIMESTAMP + + DateTime :decided_at + + foreign_key :recorded_by, :members, + type: :Bignum + + DateTime :ended_at + String :end_reason + + index :status + + # Un seul dossier en attente ou une seule adhésion + # en cours par adhérent. + index :member_id, + unique: true, + where: { status: ["pending", "accepted"], ended_at: nil } + + constraint :memberships_status_valid, + Sequel.lit("status IN ('pending', 'accepted', 'rejected')") + + constraint :memberships_decision_consistent, + Sequel.lit( + "(status = 'pending' AND decided_at IS NULL " \ + "AND recorded_by IS NULL) " \ + "OR (status IN ('accepted', 'rejected') " \ + "AND decided_at IS NOT NULL AND recorded_by IS NOT NULL)" + ) + + constraint :memberships_end_consistent, + Sequel.lit( + "(ended_at IS NULL AND end_reason IS NULL) " \ + "OR (status = 'accepted' AND ended_at IS NOT NULL " \ + "AND end_reason IN ('resignation', 'death', " \ + "'dissolution', 'exclusion'))" + ) + end + end + + down do + drop_table(:memberships) + end +end diff --git a/doc/statutes.md b/doc/statutes.md index cfb68d5..3d30ac4 100644 --- a/doc/statutes.md +++ b/doc/statutes.md @@ -28,30 +28,26 @@ Ses principes sont : - le partage ouvert des savoirs et des compétences informatiques. L'association est sans but lucratif. Ses services sont proposés sans -facturation liée à leur utilisation. Ses ressources, notamment les -cotisations des membres et les dons volontaires, servent à financer et -pérenniser ses infrastructures. Aucun bénéfice n’est distribué aux membres. +facturation liée à leur utilisation. Les dons volontaires contribuent au +financement des infrastructures et à la pérennité des services. L’association +invite les membres et les utilisateurs qui le peuvent à la soutenir +financièrement, selon leurs moyens. Cette participation est importante pour +assurer la continuité des services. Les dons sont libres, sans contrepartie +et ne conditionnent ni l’adhésion, ni les droits des membres, ni l’accès aux +services. Aucun bénéfice n’est distribué aux membres. ## Article 3 — Adhésion Peut adhérer toute personne physique ou morale acceptant les statuts et -les principes de l’association. Le Noyau examine les demandes. +les principes de l’association. Le Noyau examine les demandes et décide +de l’admission. -L’Assemblée générale (ci-après « AG ») fixe la cotisation annuelle. Après -acceptation et paiement, l’adhésion dure un an et se renouvelle à chaque -échéance si la cotisation est payée. Un paiement anticipé ou effectué -pendant le délai de relance ne change pas l’échéance. +L’adhésion prend effet à la date de son acceptation et est valable sans +limitation de durée. Aucune cotisation ni aucun don n’est exigé pour adhérer, +conserver la qualité de membre ou exercer les droits qui y sont attachés. -En cas d’impayé, une relance laisse trente jours pour payer. L’adhésion -est maintenue pendant ce délai, mais le membre n’est plus à jour de sa -cotisation : il ne peut pas voter en AG et ses accès aux services réservés -aux membres à jour peuvent être suspendus par le Noyau ou le responsable -du service. À défaut de paiement à l’expiration du délai, l’adhésion prend fin. - -L’adhésion prend également fin par démission notifiée, décès, dissolution -de la personne morale ou exclusion (article 7). La démission n’annule pas -l'obligation de régler les cotisations en retard ni celle due pour l’année -en cours. +L’adhésion prend fin par démission notifiée, décès, dissolution de la +personne morale ou exclusion (article 7). ## Article 4 — Assemblée générale @@ -89,22 +85,21 @@ modifications statutaires et à la dissolution. ### Voix et procurations -Chaque membre à jour de sa cotisation, personne physique ou morale, -dispose d’une voix. La voix d’une personne morale est exercée par son -représentant habilité. Une personne physique également membre à titre -personnel qui représente une ou plusieurs personnes morales membres -exerce une voix en son nom et une voix pour chacune d’elles. +Chaque membre, personne physique ou morale, dispose d’une voix délibérative. +La voix d’une personne morale est exercée par son représentant habilité. +Une personne physique également membre à titre personnel qui représente +une ou plusieurs personnes morales membres exerce une voix en son nom et +une voix pour chacune d’elles. -Tout membre à jour peut se faire représenter par un membre personne -physique à jour. Une personne morale donne procuration par son -représentant habilité. Une même personne peut détenir au plus deux -procurations, en plus des voix qu’elle exerce en son nom ou comme -représentant d’une ou plusieurs personnes morales. +Tout membre peut se faire représenter par un membre personne physique. +Une personne morale donne procuration par son représentant habilité. +Une même personne peut détenir au plus deux procurations, en plus des voix +qu’elle exerce en son nom ou comme représentant d’une ou plusieurs personnes +morales. -Les voix délibératives sont celles des membres à jour de leur cotisation. -Chaque voix délibérative compte séparément pour le calcul des seuils et -des quorums prévus par les statuts ; les fractions sont arrondies à -l’entier supérieur. +Chaque voix délibérative compte séparément pour le calcul des seuils et des +quorums prévus par les statuts ; les fractions sont arrondies à l’entier +supérieur. ### Décisions @@ -224,9 +219,9 @@ principes de l’article 2 ni modifier les statuts. ## Article 6 — Ressources et comptabilité -Les ressources comprennent les cotisations, dons, subventions, recettes -d’activités compatibles avec l’objet et les principes de l’association -et toute autre ressource autorisée par la loi. +Les ressources comprennent les dons volontaires, les subventions, les +recettes d’activités compatibles avec l’objet et les principes de +l’association et toute autre ressource autorisée par la loi. Les fonctions sont bénévoles. Les frais engagés pour l’association peuvent être remboursés sur justificatifs, selon les modalités du Noyau. Nul ne @@ -318,14 +313,14 @@ substantielle impose une nouvelle AG et fait repartir le délai de trente jours. La première AG ne délibère que si deux tiers au moins des voix -délibératives des membres à jour sont présentes ou représentées. À -défaut de quorum, une seconde AG est convoquée par le Noyau ou, si la -première AG a été convoquée par des membres, par ces mêmes membres. -Sa convocation est envoyée dans les quinze jours suivant le constat -de l’absence de quorum. Elle se tient au moins quinze jours après -l’envoi de sa convocation. Elle reprend le même ordre du jour et le même -projet, et ne délibère valablement que si un tiers au moins des voix -délibératives des membres à jour est présent ou représenté. +délibératives sont présentes ou représentées. À défaut de quorum, une +seconde AG est convoquée par le Noyau ou, si la première AG a été +convoquée par des membres, par ces mêmes membres. Sa convocation est +envoyée dans les quinze jours suivant le constat de l’absence de quorum. +Elle se tient au moins quinze jours après l’envoi de sa convocation. +Elle reprend le même ordre du jour et le même projet, et ne délibère +valablement que si un tiers au moins des voix délibératives est présent +ou représenté. Si le quorum de la seconde AG n’est pas atteint, le projet n’est pas adopté. Il peut être soumis à une nouvelle procédure dans les conditions @@ -334,31 +329,11 @@ du présent article. La modification ou la dissolution est adoptée à la majorité des deux tiers des suffrages exprimés. Toutefois, toute modification statutaire ayant pour effet d’augmenter les engagements des membres requiert -l’accord unanime de tous les membres, y compris ceux qui ne sont pas -à jour de leur cotisation. Cet accord peut être recueilli par écrit -en dehors de l’AG. +l’accord unanime de tous les membres. Cet accord peut être recueilli +par écrit en dehors de l’AG. En cas de dissolution, l’AG désigne un ou plusieurs liquidateurs et attribue l’actif net, après règlement des dettes, à un organisme à but non lucratif poursuivant un objet compatible avec celui de NETig. Aucun actif n’est distribué aux membres. ---- - -**Statuts adoptés par l’Assemblée générale constitutive.** - -Fait à __________________________, le ____ / ____ / ______ - -**Signatures** - -Nom : __________________________________________ - -Signature : _____________________________________ - -Nom : __________________________________________ - -Signature : _____________________________________ - -Nom : __________________________________________ - -Signature : _____________________________________