From b9c73c2cf77f2445c2bd86ef6efdce8ad2377e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 18 Oct 2022 23:12:27 +0200 Subject: [PATCH 01/18] first draft --- docu/RoadMap_2022-2023.md | 137 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docu/RoadMap_2022-2023.md diff --git a/docu/RoadMap_2022-2023.md b/docu/RoadMap_2022-2023.md new file mode 100644 index 000000000..8a29fcbb5 --- /dev/null +++ b/docu/RoadMap_2022-2023.md @@ -0,0 +1,137 @@ +# Roadmap 2022 / 2023 + +## unsortierte Sammlung von Themen + +1. backend access layer + + - Refactoring der Resolver-Klassen + - Daten-Zugriffschicht zur Kapselung der DB-Schicht + - Transfer-Datenmodel zum Austausch von Daten zwischen den Schichten + - technisches Transaktion-Handling und Lösung von Deadlocks + - Konzept in Arbeit +2. capturing alias + + - Konzept fertig + - Änderungen in Register- und Login-Prozess +3. Passwort-Verschlüsselung + + - Konzept fertig + - Unabhängigkeit von Email erzeugen + - Änderung der User-Email ermöglichen + - Versionierung der verwendeten Verschlüsselungslogik notwendig +4. Contribution-Categories + + - Bewertung und Kategorisierung von Schöpfungen: Was hat Wer für Wen geleistet? + - Regeln auf Categories ermöglichen + - Konzept in Arbeit +5. Statistics / Analysen +6. Subgruppierung / Subcommunities + + - **einfacher Ansatz:** innerhalb der existierenden Community gibt es Untergruppierungen, sprich SubCommunities + + - Einführung eine Community-Tabelle + - In der Community-Tabelle gibt es zunächst eine Haupt-Community, die mehrere Sub-Communities haben kann + - ein User ist in der Haupt-Community unique, kann aber in mehreren SubCommunities sein + - Eine SubCommunity dient zur einfachen Gruppierung gleichgesinnter oder örtlich gebundener User + - Eine SubCommunity hat eigene Moderatoren + - Motivation einer SubCommunity: kleine lokale Gruppen und jeder kennt jeden + - **ToDos**: + - DB-Migration für Community-Tabelle, User-SubCommunity-Zuordnungen, UserRights-Tabelle + - Berechtigungen für SubCommunities + - Register- und Login-Prozess für SubCommunity-Anmeldung anpassen + - Auswahl-Box einer SubCommunity + - createUser mit Zuordnung zur ausgewählten SubCommunity + - Schöpfungsprozess auf angemeldete SubCommunity anpassen + - "Beitrag einreichen"-Dialog auf angemeldete SubCommunity anpassen + - "meine Beiträge zum Gemeinwohl" mit Filter auf angemeldete SubCommunity anpassen + - "Gemeinschaft"-Dialog auf angemeldete SubCommunity anpassen + - "Mein Profil"-Dialog auf SubCommunities anpassen + - Umzug-Service in andere SubCommunity + - Löschen der Mitgliedschaft zu angemeldeter SubCommunity (Deaktivierung der Zuordnung "User-SubCommunity") + - "Senden"-Dialog mit SubCommunity-Auswahl + - "Transaktion"-Dialog mit Filter auf angemeldeter SubCommunity + - AdminInterface auf angemeldete SubCommunity anpassen + - "Übersicht"-Dialog mit Filter auf angemeldete SubCommunity + - "Nutzersuche"-Dialog mit Filter auf angemeldete SubCommunity + - "Mehrfachschöpfung"-Dialog mit Filter auf angemeldete SubComunity + - Subject/Texte/Footer/... der Email-Benachrichtigungen auf angemeldete SubCommunity anpassen + - **komplexer Ansatz** + + - DB-Migration + + - Community-Tabelle mit Eintrag für Haupt-Community + - Community-User Zuordnungstabelle + - Account-Tabelle + - Account-User Zuordnungstabelle + - SubCommunity-Verwaltung + + - Neuanlage + - Änderungen + - Berechtigungen (Admin, Moderator, User, ...) + - Konten für 2te und 3te Schöpfung + - User-Community-Verwaltung + + - Zuordnung zu einer SubCommunity bei Register bzw Login + - Umzug in andere SubCommunity + - Eindeutigkeit Community-übergreifend? + - User-Account-Verwaltung + + - Berechtigung auf Accounts anderer User (Treuhander) + - Transaktions- und Schöpfungslogik auf Multi-Community anpassen +7. User-Beziehungen und Favoritenverwaltung + + - User-User-Zuordnung + - aus Tx-Liste die aktuellen Favoriten ermitteln + - Verwaltung von Zuordnungen + - Auswahl + - Berechtigungen + - Gruppierung + - Community-übergreifend + - User-Beziehungen +8. technische Ablösung der Email und Ersatz durch GradidoID +9. Zeitzone + + - User sieht immer seine Locale-Zeit und Monate + - Admin sieht immer UTC-Zeit und Monate + - wichtiges Kriterium für Schöpfung ist das TargetDate + - Berechnung der möglichen Schöpfungen muss somit auf dem TargetDate der Schöpfung ermittelt werden! + - Kann es vorkommen, dass das TargetDate der Contribution vor dem CreationDate der TX liegt? + - Contribution-Link aktiviert in Tokyo am Locale: 01.11.2022 07:00:00+09:00 = TargetDate = Zieldatum der Schöpfung + - Gebucht wird die TX mit creationDate=31.10.2022 22:00:00 UTC, + - die Schöpfung hat creationDate=31.10.2022 22:00:00 UTC und contributionDate=01.11.2022 07:00:00 und neu contributionOffset=+09:00 + - **Prüfung auf -12h <= ClientRequestTime <= +12h** + - original ClientRequestTime in DB speichern + - 17.10.2022 22:00 +09:00 => 17.10.2022 UTC: 17.10.2022 13:00 UTC => 17.10.2022 + - 18.10.2022 02:00 +09:00 => 18.10.2022 UTC: 17.10.2022 17:00 UTC => 17.10.2022 !!!! darf nicht weil gleicher Tag !!! + - 31.10.2022 22:00 +09:00 => 10.2022 + - 01.11.2022 07:00 +09:00 => 11.2022 +10. Layout +11. Manuelle User-Registrierung für Admin + + 1. 10.12.2022 Tag bei den Galliern +12. Dezentralisierung / Federation + + 1. Hyperswarm + 2. + +## Priorisierung + +1. capturing alias +2. Manuelle User-Registrierung für Admin (10.12.2022) **Konzeption!!**! +3. Zeitzone +4. User-Beziehungen und Favoritenverwaltung +5. Layout +6. Passwort-Verschlüsselung +7. +8. Subgruppierung / Subcommunities (einfacher Ansatz) +9. Contribution-Categories +10. +11. backend access layer +12. +13. Statistics / Analysen +14. +15. technische Ablösung der Email und Ersatz durch GradidoID +16. +17. Dezentralisierung / Federation + +## Zeitleiste From f0187c130119a4a9711e6b802c6ba47071d69bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 18 Oct 2022 23:55:22 +0200 Subject: [PATCH 02/18] first draft after meeting --- docu/RoadMap_2022-2023.md | 92 ++++++++++++-------------- docu/graphics/RoadMap2022-2023.drawio | 60 +++++++++++++++++ docu/graphics/RoadMap2022-2023.png | Bin 0 -> 64646 bytes 3 files changed, 103 insertions(+), 49 deletions(-) create mode 100644 docu/graphics/RoadMap2022-2023.drawio create mode 100644 docu/graphics/RoadMap2022-2023.png diff --git a/docu/RoadMap_2022-2023.md b/docu/RoadMap_2022-2023.md index 8a29fcbb5..26a4ec41e 100644 --- a/docu/RoadMap_2022-2023.md +++ b/docu/RoadMap_2022-2023.md @@ -55,29 +55,6 @@ - "Nutzersuche"-Dialog mit Filter auf angemeldete SubCommunity - "Mehrfachschöpfung"-Dialog mit Filter auf angemeldete SubComunity - Subject/Texte/Footer/... der Email-Benachrichtigungen auf angemeldete SubCommunity anpassen - - **komplexer Ansatz** - - - DB-Migration - - - Community-Tabelle mit Eintrag für Haupt-Community - - Community-User Zuordnungstabelle - - Account-Tabelle - - Account-User Zuordnungstabelle - - SubCommunity-Verwaltung - - - Neuanlage - - Änderungen - - Berechtigungen (Admin, Moderator, User, ...) - - Konten für 2te und 3te Schöpfung - - User-Community-Verwaltung - - - Zuordnung zu einer SubCommunity bei Register bzw Login - - Umzug in andere SubCommunity - - Eindeutigkeit Community-übergreifend? - - User-Account-Verwaltung - - - Berechtigung auf Accounts anderer User (Treuhander) - - Transaktions- und Schöpfungslogik auf Multi-Community anpassen 7. User-Beziehungen und Favoritenverwaltung - User-User-Zuordnung @@ -89,30 +66,47 @@ - Community-übergreifend - User-Beziehungen 8. technische Ablösung der Email und Ersatz durch GradidoID + + * APIs / Links / etc mit Email anpassen, so dass keine Email mehr verwendet wird + * Email soll aber im Aussen für User optional noch verwendbar bleiben + * Intern erfolgt aber auf jedenfall ein Mapping auf GradidoID egal ob per Email oder Alias angefragt wird 9. Zeitzone - User sieht immer seine Locale-Zeit und Monate - Admin sieht immer UTC-Zeit und Monate - - wichtiges Kriterium für Schöpfung ist das TargetDate - - Berechnung der möglichen Schöpfungen muss somit auf dem TargetDate der Schöpfung ermittelt werden! - - Kann es vorkommen, dass das TargetDate der Contribution vor dem CreationDate der TX liegt? - - Contribution-Link aktiviert in Tokyo am Locale: 01.11.2022 07:00:00+09:00 = TargetDate = Zieldatum der Schöpfung - - Gebucht wird die TX mit creationDate=31.10.2022 22:00:00 UTC, - - die Schöpfung hat creationDate=31.10.2022 22:00:00 UTC und contributionDate=01.11.2022 07:00:00 und neu contributionOffset=+09:00 - - **Prüfung auf -12h <= ClientRequestTime <= +12h** - - original ClientRequestTime in DB speichern - - 17.10.2022 22:00 +09:00 => 17.10.2022 UTC: 17.10.2022 13:00 UTC => 17.10.2022 - - 18.10.2022 02:00 +09:00 => 18.10.2022 UTC: 17.10.2022 17:00 UTC => 17.10.2022 !!!! darf nicht weil gleicher Tag !!! - - 31.10.2022 22:00 +09:00 => 10.2022 - - 01.11.2022 07:00 +09:00 => 11.2022 + - wichtiges Kriterium für Schöpfung ist das TargetDate ( heißt in DB contributionDate) + - Berechnung der möglichen Schöpfungen muss somit auf dem TargetDate der Schöpfung ermittelt werden! **(Ist-Zustand)** + - Kann es vorkommen, dass das TargetDate der Contribution vor dem CreationDate der TX liegt? Ja + - Beispiel: User in Tokyo Locale mit Offest +09:00 + + - aktiviert Contribution-Link mit Locale: 01.11.2022 07:00:00+09:00 = TargetDate = Zieldatum der Schöpfung + - die Contribution wird gespeichert mit + + - creationDate=31.10.2022 22:00:00 UTC + - contributionDate=01.11.2022 07:00:00 + - (neu) clientRequestTime=01.11.2022 07:00:00+09:00 + - durch automatische Bestätigung und sofortiger Transaktion wird die TX gespeichert mit + + - creationDate=31.10.2022 22:00:00 UTC + - **zwingende Prüfung aller Requeste: auf -12h <= ClientRequestTime <= +12h** + - zur Analyse und Problemverfolgung von Contributions immer original ClientRequestTime mit Offset in DB speichern + - Beispiel für täglichen Contribution-Link während des Monats: + + - 17.10.2022 22:00 +09:00 => 17.10.2022 UTC: 17.10.2022 13:00 UTC => 17.10.2022 + - 18.10.2022 02:00 +09:00 => 18.10.2022 UTC: 17.10.2022 17:00 UTC => 17.10.2022 !!!! darf nicht weil gleicher Tag !!! + - Beispiel für täglichen Contribution-Link am Monatswechsel: + + - 31.10.2022 22:00 +09:00 => 31.10.2022 UTC: 31.10.2022 15:00 UTC => 31.10.2022 + - 01.11.2022 07:00 +09:00 => 01.11.2022 UTC: 31.10.2022 22:00 UTC => 31.10.2022 !!!! darf nicht weil gleicher Tag !!! 10. Layout 11. Manuelle User-Registrierung für Admin - 1. 10.12.2022 Tag bei den Galliern + - soll am 10.12.2022 für den Tag bei den Galliern produktiv sein 12. Dezentralisierung / Federation - 1. Hyperswarm - 2. + - Hyperswarm + - Authentifizierungs- und Autorisierungs-Handshake + - Inter-Community-Communication ## Priorisierung @@ -122,16 +116,16 @@ 4. User-Beziehungen und Favoritenverwaltung 5. Layout 6. Passwort-Verschlüsselung -7. -8. Subgruppierung / Subcommunities (einfacher Ansatz) -9. Contribution-Categories -10. -11. backend access layer -12. -13. Statistics / Analysen -14. -15. technische Ablösung der Email und Ersatz durch GradidoID -16. -17. Dezentralisierung / Federation +7. Subgruppierung / Subcommunities (einfacher Ansatz) +8. Contribution-Categories +9. backend access layer +10. Statistics / Analysen +11. technische Ablösung der Email und Ersatz durch GradidoID +12. Dezentralisierung / Federation ## Zeitleiste + + + + +![img](./graphics/RoadMap2022-2023.png) diff --git a/docu/graphics/RoadMap2022-2023.drawio b/docu/graphics/RoadMap2022-2023.drawio new file mode 100644 index 000000000..58b8dec94 --- /dev/null +++ b/docu/graphics/RoadMap2022-2023.drawio @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docu/graphics/RoadMap2022-2023.png b/docu/graphics/RoadMap2022-2023.png new file mode 100644 index 0000000000000000000000000000000000000000..3ce8511a346c0cec5a9293a0da6b5ddc6b1d8509 GIT binary patch literal 64646 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 Date: Tue, 25 Oct 2022 22:10:58 +0200 Subject: [PATCH 03/18] Ergebnisse aus Architekur-Mtg vom 25.10.2022 --- docu/RoadMap_2022-2023.md | 201 ++++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 83 deletions(-) diff --git a/docu/RoadMap_2022-2023.md b/docu/RoadMap_2022-2023.md index 26a4ec41e..be3e197bd 100644 --- a/docu/RoadMap_2022-2023.md +++ b/docu/RoadMap_2022-2023.md @@ -13,49 +13,47 @@ - Konzept fertig - Änderungen in Register- und Login-Prozess -3. Passwort-Verschlüsselung +3. Passwort-Verschlüsselung: Refactoring - - Konzept fertig - - Unabhängigkeit von Email erzeugen - - Änderung der User-Email ermöglichen - - Versionierung der verwendeten Verschlüsselungslogik notwendig -4. Contribution-Categories + - Konzept aufteilen in Ausbaustufen + - Altlasten entsorgen + - Versionierung/Typisierung der verwendeten Verschlüsselungslogik notwendig + - DB-Migration auf encryptionType=EMAIL +4. Passwort-Verschlüsselung: Login mit impliziter Neuverschlüsselung + + * Logik der Passwortverschlüsselung auf GradidoID einführen + * bei Login mit encryptionType=Email oder OneTime triggern einer Neuverschlüsselung per GradidoID + * Unabhängigkeit von Email erzeugen + * Änderung der User-Email ermöglichen +5. Contribution-Categories - Bewertung und Kategorisierung von Schöpfungen: Was hat Wer für Wen geleistet? - Regeln auf Categories ermöglichen - Konzept in Arbeit -5. Statistics / Analysen -6. Subgruppierung / Subcommunities +6. Statistics / Analysen +7. Contribution-Link editieren +8. User-Tagging - - **einfacher Ansatz:** innerhalb der existierenden Community gibt es Untergruppierungen, sprich SubCommunities + - Eine UserTag dient zur einfachen Gruppierung gleichgesinnter oder örtlich gebundener User + - Motivation des User-Taggings: bilden kleinerer lokaler User-Gruppen und jeder kennt jeden + - Einführung einer UserTaggings-Tabelle und eine User-UserTaggings-Zuordnungs-Tabelle + - Ein Moderator kann im AdminInterface die Liste der UserTags pflegen - - Einführung eine Community-Tabelle - - In der Community-Tabelle gibt es zunächst eine Haupt-Community, die mehrere Sub-Communities haben kann - - ein User ist in der Haupt-Community unique, kann aber in mehreren SubCommunities sein - - Eine SubCommunity dient zur einfachen Gruppierung gleichgesinnter oder örtlich gebundener User - - Eine SubCommunity hat eigene Moderatoren - - Motivation einer SubCommunity: kleine lokale Gruppen und jeder kennt jeden - - **ToDos**: - - DB-Migration für Community-Tabelle, User-SubCommunity-Zuordnungen, UserRights-Tabelle - - Berechtigungen für SubCommunities - - Register- und Login-Prozess für SubCommunity-Anmeldung anpassen - - Auswahl-Box einer SubCommunity - - createUser mit Zuordnung zur ausgewählten SubCommunity - - Schöpfungsprozess auf angemeldete SubCommunity anpassen - - "Beitrag einreichen"-Dialog auf angemeldete SubCommunity anpassen - - "meine Beiträge zum Gemeinwohl" mit Filter auf angemeldete SubCommunity anpassen - - "Gemeinschaft"-Dialog auf angemeldete SubCommunity anpassen - - "Mein Profil"-Dialog auf SubCommunities anpassen - - Umzug-Service in andere SubCommunity - - Löschen der Mitgliedschaft zu angemeldeter SubCommunity (Deaktivierung der Zuordnung "User-SubCommunity") - - "Senden"-Dialog mit SubCommunity-Auswahl - - "Transaktion"-Dialog mit Filter auf angemeldeter SubCommunity - - AdminInterface auf angemeldete SubCommunity anpassen - - "Übersicht"-Dialog mit Filter auf angemeldete SubCommunity - - "Nutzersuche"-Dialog mit Filter auf angemeldete SubCommunity - - "Mehrfachschöpfung"-Dialog mit Filter auf angemeldete SubComunity - - Subject/Texte/Footer/... der Email-Benachrichtigungen auf angemeldete SubCommunity anpassen -7. User-Beziehungen und Favoritenverwaltung + - neues TAG anlegen + - vorhandenes TAG umbenennen + - ein TAG löschen, sofern kein User mehr diesem TAG zugeordnet ist + - Will ein User ein TAG zugeordnet werden, so kann dies nur ein Moderator im AdminInterface tun + - Ein Moderator kann im AdminInterface + + - ein TAG einem User zuordnen + - ein TAG von einem User entfernen + - wichtige UseCases: + + - Zuordnung eines Users zu einem TAG durch einen Moderator + - TAG spezifische Schöpfung + - User muss für seinen Beitrag ein TAG auswählen können, dem er zuvor zugeordnet wurde + - TAG-Moderator kann den Beitrag bestätigen, weil er den User mit dem TAG (persönlich) kennt +9. User-Beziehungen und Favoritenverwaltung - User-User-Zuordnung - aus Tx-Liste die aktuellen Favoriten ermitteln @@ -65,67 +63,104 @@ - Gruppierung - Community-übergreifend - User-Beziehungen -8. technische Ablösung der Email und Ersatz durch GradidoID +10. technische Ablösung der Email und Ersatz durch GradidoID - * APIs / Links / etc mit Email anpassen, so dass keine Email mehr verwendet wird - * Email soll aber im Aussen für User optional noch verwendbar bleiben - * Intern erfolgt aber auf jedenfall ein Mapping auf GradidoID egal ob per Email oder Alias angefragt wird -9. Zeitzone + * APIs / Links / etc mit Email anpassen, so dass keine Email mehr verwendet wird + * Email soll aber im Aussen für User optional noch verwendbar bleiben + * Intern erfolgt aber auf jedenfall ein Mapping auf GradidoID egal ob per Email oder Alias angefragt wird +11. Zeitzone - - User sieht immer seine Locale-Zeit und Monate - - Admin sieht immer UTC-Zeit und Monate - - wichtiges Kriterium für Schöpfung ist das TargetDate ( heißt in DB contributionDate) - - Berechnung der möglichen Schöpfungen muss somit auf dem TargetDate der Schöpfung ermittelt werden! **(Ist-Zustand)** - - Kann es vorkommen, dass das TargetDate der Contribution vor dem CreationDate der TX liegt? Ja - - Beispiel: User in Tokyo Locale mit Offest +09:00 + - User sieht immer seine Locale-Zeit und Monate + - Admin sieht immer UTC-Zeit und Monate + - wichtiges Kriterium für Schöpfung ist das TargetDate ( heißt in DB contributionDate) + - Berechnung der möglichen Schöpfungen muss somit auf dem TargetDate der Schöpfung ermittelt werden! **(Ist-Zustand)** + - Kann es vorkommen, dass das TargetDate der Contribution vor dem CreationDate der TX liegt? Ja + - Beispiel: User in Tokyo Locale mit Offest +09:00 - - aktiviert Contribution-Link mit Locale: 01.11.2022 07:00:00+09:00 = TargetDate = Zieldatum der Schöpfung - - die Contribution wird gespeichert mit + - aktiviert Contribution-Link mit Locale: 01.11.2022 07:00:00+09:00 = TargetDate = Zieldatum der Schöpfung + - die Contribution wird gespeichert mit - - creationDate=31.10.2022 22:00:00 UTC - - contributionDate=01.11.2022 07:00:00 - - (neu) clientRequestTime=01.11.2022 07:00:00+09:00 - - durch automatische Bestätigung und sofortiger Transaktion wird die TX gespeichert mit + - creationDate=31.10.2022 22:00:00 UTC + - contributionDate=01.11.2022 07:00:00 + - (neu) clientRequestTime=01.11.2022 07:00:00+09:00 + - durch automatische Bestätigung und sofortiger Transaktion wird die TX gespeichert mit - - creationDate=31.10.2022 22:00:00 UTC - - **zwingende Prüfung aller Requeste: auf -12h <= ClientRequestTime <= +12h** - - zur Analyse und Problemverfolgung von Contributions immer original ClientRequestTime mit Offset in DB speichern - - Beispiel für täglichen Contribution-Link während des Monats: + - creationDate=31.10.2022 22:00:00 UTC + - **zwingende Prüfung aller Requeste: auf -12h <= ClientRequestTime <= +12h** - - 17.10.2022 22:00 +09:00 => 17.10.2022 UTC: 17.10.2022 13:00 UTC => 17.10.2022 - - 18.10.2022 02:00 +09:00 => 18.10.2022 UTC: 17.10.2022 17:00 UTC => 17.10.2022 !!!! darf nicht weil gleicher Tag !!! - - Beispiel für täglichen Contribution-Link am Monatswechsel: + - Prüfung auf Sommerzeiten und exotische Länder beachten + - + - zur Analyse und Problemverfolgung von Contributions immer original ClientRequestTime mit Offset in DB speichern + - Beispiel für täglichen Contribution-Link während des Monats: - - 31.10.2022 22:00 +09:00 => 31.10.2022 UTC: 31.10.2022 15:00 UTC => 31.10.2022 - - 01.11.2022 07:00 +09:00 => 01.11.2022 UTC: 31.10.2022 22:00 UTC => 31.10.2022 !!!! darf nicht weil gleicher Tag !!! -10. Layout -11. Manuelle User-Registrierung für Admin + - 17.10.2022 22:00 +09:00 => 17.10.2022 UTC: 17.10.2022 13:00 UTC => 17.10.2022 + - 18.10.2022 02:00 +09:00 => 18.10.2022 UTC: 17.10.2022 17:00 UTC => 17.10.2022 !!!! darf nicht weil gleicher Tag !!! + - Beispiel für täglichen Contribution-Link am Monatswechsel: + + - 31.10.2022 22:00 +09:00 => 31.10.2022 UTC: 31.10.2022 15:00 UTC => 31.10.2022 + - 01.11.2022 07:00 +09:00 => 01.11.2022 UTC: 31.10.2022 22:00 UTC => 31.10.2022 !!!! darf nicht weil gleicher Tag !!! +12. Layout +13. Lastschriften-Link +14. Registrierung mit Redeem-Link: bei inaktivem Konto keine Buchung möglich + + 1. speichern des Links zusammen mit OptIn-Code + 2. +15. Manuelle User-Registrierung für Admin - soll am 10.12.2022 für den Tag bei den Galliern produktiv sein -12. Dezentralisierung / Federation +16. Dezentralisierung / Federation - Hyperswarm + + - funktioniert schon im Prototyp + - alle Instanzen finden sich gegenseitig + - ToDo: + - Infos aus HyperSwarm in der Community speichern + - Prüfung ob neue mir noch unbekannte Community hinzugekommen ist? + - Triggern der Authentifizierungs- und Autorisierungs-Handshake für neue Community - Authentifizierungs- und Autorisierungs-Handshake - Inter-Community-Communication + - **ToDos**: + + - DB-Migration für Community-Tabelle, User-Community-Zuordnungen, UserRights-Tabelle + - Berechtigungen für Communities + - Register- und Login-Prozess für Community-Anmeldung anpassen + + - Auswahl-Box einer Community + - createUser mit Zuordnung zur ausgewählten Community + - Schöpfungsprozess auf angemeldete Community anpassen + + - "Beitrag einreichen"-Dialog auf angemeldete Community anpassen + - "meine Beiträge zum Gemeinwohl" mit Filter auf angemeldete Community anpassen + - "Gemeinschaft"-Dialog auf angemeldete Community anpassen + - "Mein Profil"-Dialog auf Communities anpassen + + - Umzug-Service in andere Community + - Löschen der Mitgliedschaft zu angemeldeter Community (Deaktivierung der Zuordnung "User-Community") + - "Senden"-Dialog mit Community-Auswahl + - "Transaktion"-Dialog mit Filter auf angemeldeter Community + - AdminInterface auf angemeldete Community anpassen + + - "Übersicht"-Dialog mit Filter auf angemeldete Community + - "Nutzersuche"-Dialog mit Filter auf angemeldete Community + - "Mehrfachschöpfung"-Dialog mit Filter auf angemeldete Comunity + - Subject/Texte/Footer/... der Email-Benachrichtigungen auf angemeldete Community anpassen ## Priorisierung -1. capturing alias -2. Manuelle User-Registrierung für Admin (10.12.2022) **Konzeption!!**! -3. Zeitzone -4. User-Beziehungen und Favoritenverwaltung +1. Contribution-Link editieren (vlt schon im vorherigen Bugfix-Release Ende Okt. 2022 fertig) +2. Passwort-Verschlüsselung: Refactoring **Konzeption fertig!!**! +3. Manuelle User-Registrierung für Admin (10.12.2022) **Konzeption ongoing!!**! +4. Passwort-Verschlüsselung: implizite Login-Neuverschlüsselung **Konzeption fertig!!**! 5. Layout -6. Passwort-Verschlüsselung -7. Subgruppierung / Subcommunities (einfacher Ansatz) -8. Contribution-Categories -9. backend access layer -10. Statistics / Analysen -11. technische Ablösung der Email und Ersatz durch GradidoID -12. Dezentralisierung / Federation - -## Zeitleiste - - - - -![img](./graphics/RoadMap2022-2023.png) +6. Zeitzone +7. Dezentralisierung / Federation +8. capturing alias **Konzeption fertig!!**! +9. Registrierung mit Redeem-Link: bei inaktivem Konto keine Buchung möglich +10. Subgruppierung / User-Tagging (einfacher Ansatz) +11. backend access layer +12. technische Ablösung der Email und Ersatz durch GradidoID +13. User-Beziehungen und Favoritenverwaltung +14. Lastschriften-Link +15. Contribution-Categories +16. Statistics / Analysen From 77227355d8760d8e481b04f66706c53c933d2256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 25 Oct 2022 22:27:30 +0200 Subject: [PATCH 04/18] =?UTF-8?q?kleine=20=C3=84nderungen=20u=20Erg=C3=A4n?= =?UTF-8?q?zungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docu/RoadMap_2022-2023.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docu/RoadMap_2022-2023.md b/docu/RoadMap_2022-2023.md index be3e197bd..fa8573448 100644 --- a/docu/RoadMap_2022-2023.md +++ b/docu/RoadMap_2022-2023.md @@ -101,10 +101,11 @@ - 01.11.2022 07:00 +09:00 => 01.11.2022 UTC: 31.10.2022 22:00 UTC => 31.10.2022 !!!! darf nicht weil gleicher Tag !!! 12. Layout 13. Lastschriften-Link -14. Registrierung mit Redeem-Link: bei inaktivem Konto keine Buchung möglich +14. Registrierung mit Redeem-Link: - 1. speichern des Links zusammen mit OptIn-Code - 2. + * bei inaktivem Konto, sprich bisher noch keine Email-Bestätigung, keine Buchung möglich + * somit speichern des Links zusammen mit OptIn-Code + * damit kann in einem Resend der ConfirmationEmail der Link auch korrekt wieder mitgeliefert werden 15. Manuelle User-Registrierung für Admin - soll am 10.12.2022 für den Tag bei den Galliern produktiv sein From 885099aa75ffd2eb3f57f292f0a85838b19f88c4 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Sat, 12 Nov 2022 19:57:53 +0100 Subject: [PATCH 05/18] moved all jest related packages in the `devDependencies` section, since thats where they belong --- backend/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/package.json b/backend/package.json index 1db683b2a..ac8022268 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,11 +19,9 @@ }, "dependencies": { "@hyperswarm/dht": "^6.2.0", - "@types/jest": "^27.0.2", "@types/lodash.clonedeep": "^4.5.6", "@types/uuid": "^8.3.4", "apollo-server-express": "^2.25.2", - "apollo-server-testing": "^2.25.2", "axios": "^0.21.1", "class-validator": "^0.13.1", "cors": "^2.8.5", @@ -32,7 +30,6 @@ "dotenv": "^10.0.0", "express": "^4.17.1", "graphql": "^15.5.1", - "jest": "^27.2.4", "jsonwebtoken": "^8.5.1", "lodash.clonedeep": "^4.5.0", "log4js": "^6.4.6", @@ -41,18 +38,19 @@ "random-bigint": "^0.0.1", "reflect-metadata": "^0.1.13", "sodium-native": "^3.3.0", - "ts-jest": "^27.0.5", "type-graphql": "^1.1.1", "uuid": "^8.3.2" }, "devDependencies": { "@types/express": "^4.17.12", "@types/faker": "^5.5.9", + "@types/jest": "^27.0.2", "@types/jsonwebtoken": "^8.5.2", "@types/node": "^16.10.3", "@types/nodemailer": "^6.4.4", "@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/parser": "^4.28.0", + "apollo-server-testing": "^2.25.2", "eslint": "^7.29.0", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^16.0.3", @@ -60,10 +58,12 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-promise": "^5.1.0", + "jest": "^27.2.4", "faker": "^5.5.3", "nodemon": "^2.0.7", "prettier": "^2.3.1", "ts-node": "^10.0.0", + "ts-jest": "^27.0.5", "tsconfig-paths": "^3.14.0", "typescript": "^4.3.4" } From 48f66dc7d2f601e49ce5f5773727ccf852057711 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Sat, 12 Nov 2022 20:02:29 +0100 Subject: [PATCH 06/18] use proper order (alphabetical) --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index ac8022268..5c0de34f3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -62,8 +62,8 @@ "faker": "^5.5.3", "nodemon": "^2.0.7", "prettier": "^2.3.1", - "ts-node": "^10.0.0", "ts-jest": "^27.0.5", + "ts-node": "^10.0.0", "tsconfig-paths": "^3.14.0", "typescript": "^4.3.4" } From 3dfffbdfea14502b0b5744364ddbd8db67c7807b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Sat, 12 Nov 2022 20:03:45 +0100 Subject: [PATCH 07/18] more order fixes --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 5c0de34f3..b061ce0d3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -58,8 +58,8 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-promise": "^5.1.0", - "jest": "^27.2.4", "faker": "^5.5.3", + "jest": "^27.2.4", "nodemon": "^2.0.7", "prettier": "^2.3.1", "ts-jest": "^27.0.5", From 78451f0d9f5aab5fad98e41faae9958ca7c4da0c Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Sat, 12 Nov 2022 20:05:09 +0100 Subject: [PATCH 08/18] move type definitions into `devDependencies` --- backend/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index b061ce0d3..652650d56 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,8 +19,6 @@ }, "dependencies": { "@hyperswarm/dht": "^6.2.0", - "@types/lodash.clonedeep": "^4.5.6", - "@types/uuid": "^8.3.4", "apollo-server-express": "^2.25.2", "axios": "^0.21.1", "class-validator": "^0.13.1", @@ -46,8 +44,10 @@ "@types/faker": "^5.5.9", "@types/jest": "^27.0.2", "@types/jsonwebtoken": "^8.5.2", + "@types/lodash.clonedeep": "^4.5.6", "@types/node": "^16.10.3", "@types/nodemailer": "^6.4.4", + "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^4.28.0", "@typescript-eslint/parser": "^4.28.0", "apollo-server-testing": "^2.25.2", From b3e880d45f339293d1a8cb49d04ad85bd816ee82 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 24 Nov 2022 13:03:50 +0100 Subject: [PATCH 09/18] undo refactoring --- .../resolver/TransactionLinkResolver.ts | 5 +- .../resolver/TransactionResolver.test.ts | 70 +++++++++---------- .../graphql/resolver/TransactionResolver.ts | 33 ++++----- backend/src/util/utilities.ts | 12 ---- backend/src/util/validate.ts | 35 +++------- 5 files changed, 59 insertions(+), 96 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index a5c4a5f01..1b3558bb2 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -74,7 +74,10 @@ export class TransactionLinkResolver { const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay) // validate amount - await calculateBalance(user.id, holdAvailableAmount, createdDate) + const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate) + if (!sendBalance) { + throw new Error("user hasn't enough GDD or amount is < 0") + } const transactionLink = dbTransactionLink.create() transactionLink.userId = user.id diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 9e74623c8..86cdf5314 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -16,7 +16,7 @@ import { stephenHawking } from '@/seeds/users/stephen-hawking' import { EventProtocol } from '@entity/EventProtocol' import { Transaction } from '@entity/Transaction' import { User } from '@entity/User' -import { cleanDB, resetToken, testEnvironment } from '@test/helpers' +import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { GraphQLError } from 'graphql' import { findUserByEmail } from './UserResolver' @@ -253,50 +253,21 @@ describe('send coins', () => { }), ).toEqual( expect.objectContaining({ - errors: [new GraphQLError(`User has not received any GDD yet`)], + errors: [new GraphQLError(`user hasn't enough GDD or amount is < 0`)], }), ) }) it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - `No prior transaction found for user with id: ${user[1].id}`, + `user hasn't enough GDD or amount is < 0 : balance=null`, ) }) }) - - describe('sending negative amount', () => { - it('throws an error', async () => { - jest.clearAllMocks() - expect( - await mutate({ - mutation: sendCoins, - variables: { - email: 'peter@lustig.de', - amount: -50, - memo: 'testing negative', - }, - }), - ).toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Transaction amount must be greater than 0')], - }), - ) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Transaction amount must be greater than 0: -50') - }) - }) }) describe('user has some GDD', () => { beforeAll(async () => { - resetToken() - - // login as bob again - await query({ mutation: login, variables: bobData }) - // create contribution as user bob const contribution = await mutate({ mutation: createContribution, @@ -316,6 +287,35 @@ describe('send coins', () => { await query({ mutation: login, variables: bobData }) }) + afterAll(async () => { + await cleanDB() + }) + + describe('trying to send negative amount', () => { + it('throws an error', async () => { + expect( + await mutate({ + mutation: sendCoins, + variables: { + email: 'peter@lustig.de', + amount: -50, + memo: 'testing negative', + }, + }), + ).toEqual( + expect.objectContaining({ + errors: [new GraphQLError(`user hasn't enough GDD or amount is < 0`)], + }), + ) + }) + + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + `user hasn't enough GDD or amount is < 0 : balance=null`, + ) + }) + }) + describe('good transaction', () => { it('sends the coins', async () => { expect( @@ -324,7 +324,7 @@ describe('send coins', () => { variables: { email: 'peter@lustig.de', amount: 50, - memo: 'unrepeatable memo', + memo: 'unrepeateable memo', }, }), ).toEqual( @@ -340,7 +340,7 @@ describe('send coins', () => { // Find the exact transaction (sent one is the one with user[1] as user) const transaction = await Transaction.find({ userId: user[1].id, - memo: 'unrepeatable memo', + memo: 'unrepeateable memo', }) expect(EventProtocol.find()).resolves.toContainEqual( @@ -357,7 +357,7 @@ describe('send coins', () => { // Find the exact transaction (received one is the one with user[0] as user) const transaction = await Transaction.find({ userId: user[0].id, - memo: 'unrepeatable memo', + memo: 'unrepeateable memo', }) expect(EventProtocol.find()).resolves.toContainEqual( diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index f0fb2f452..594039cfd 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -39,7 +39,6 @@ import { findUserByEmail } from './UserResolver' import { sendTransactionLinkRedeemedEmail } from '@/mailer/sendTransactionLinkRedeemed' import { Event, EventTransactionReceive, EventTransactionSend } from '@/event/Event' import { eventProtocol } from '@/event/EventProtocolEmitter' -import { Decay } from '../model/Decay' export const executeTransaction = async ( amount: Decimal, @@ -69,8 +68,17 @@ export const executeTransaction = async ( // validate amount const receivedCallDate = new Date() - - const sendBalance = await calculateBalance(sender.id, amount, receivedCallDate, transactionLink) + const sendBalance = await calculateBalance( + sender.id, + amount.mul(-1), + receivedCallDate, + transactionLink, + ) + logger.debug(`calculated Balance=${sendBalance}`) + if (!sendBalance) { + logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`) + throw new Error("user hasn't enough GDD or amount is < 0") + } const queryRunner = getConnection().createQueryRunner() await queryRunner.connect() @@ -100,24 +108,7 @@ export const executeTransaction = async ( transactionReceive.userId = recipient.id transactionReceive.linkedUserId = sender.id transactionReceive.amount = amount - - // state received balance - let receiveBalance: { - balance: Decimal - decay: Decay - lastTransactionId: number - } | null - - // try received balance - try { - receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) - } catch (e) { - logger.info( - `User with no transactions sent: ${recipient.id}, has received a transaction of ${amount} GDD from user: ${sender.id}`, - ) - receiveBalance = null - } - + const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount transactionReceive.balanceDate = receivedCallDate transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0) diff --git a/backend/src/util/utilities.ts b/backend/src/util/utilities.ts index 65214ebb5..9abb31554 100644 --- a/backend/src/util/utilities.ts +++ b/backend/src/util/utilities.ts @@ -1,17 +1,5 @@ -import Decimal from 'decimal.js-light' - export const objectValuesToArray = (obj: { [x: string]: string }): Array => { return Object.keys(obj).map(function (key) { return obj[key] }) } - -// to improve code readability, as String is needed, it is handled inside this utility function -export const decimalAddition = (a: Decimal, b: Decimal): Decimal => { - return a.add(b.toString()) -} - -// to improve code readability, as String is needed, it is handled inside this utility function -export const decimalSubtraction = (a: Decimal, b: Decimal): Decimal => { - return a.minus(b.toString()) -} diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index 9640cc614..8d1c90ca4 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -5,8 +5,6 @@ import { Decay } from '@model/Decay' import { getCustomRepository } from '@dbTools/typeorm' import { TransactionLinkRepository } from '@repository/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' -import { decimalSubtraction, decimalAddition } from './utilities' -import { backendLogger as logger } from '@/server/logger' function isStringBoolean(value: string): boolean { const lowerValue = value.toLowerCase() @@ -25,26 +23,14 @@ async function calculateBalance( amount: Decimal, time: Date, transactionLink?: dbTransactionLink | null, -): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number }> { - // negative or empty amount should not be allowed - if (amount.lessThanOrEqualTo(0)) { - logger.error(`Transaction amount must be greater than 0: ${amount}`) - throw new Error('Transaction amount must be greater than 0') - } - - // check if user has prior transactions +): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> { const lastTransaction = await Transaction.findOne({ userId }, { order: { balanceDate: 'DESC' } }) - - if (!lastTransaction) { - logger.error(`No prior transaction found for user with id: ${userId}`) - throw new Error('User has not received any GDD yet') - } + if (!lastTransaction) return null const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time) - // new balance is the old balance minus the amount used - const balance = decimalSubtraction(decay.balance, amount) - + // TODO why we have to use toString() here? + const balance = decay.balance.add(amount.toString()) const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time) @@ -52,16 +38,11 @@ async function calculateBalance( // else we cannot redeem links which are more or equal to half of what an account actually owns const releasedLinkAmount = transactionLink ? transactionLink.holdAvailableAmount : new Decimal(0) - const availableBalance = decimalSubtraction(balance, sumHoldAvailableAmount) - - if (decimalAddition(availableBalance, releasedLinkAmount).lessThan(0)) { - logger.error( - `Not enough funds for a transaction of ${amount} GDD, user with id: ${userId} has only ${balance} GDD available`, - ) - throw new Error('Not enough funds for transaction') + if ( + balance.minus(sumHoldAvailableAmount.toString()).plus(releasedLinkAmount.toString()).lessThan(0) + ) { + return null } - - logger.debug(`calculated Balance=${balance}`) return { balance, lastTransactionId: lastTransaction.id, decay } } From 77d93f18fa5c79982dd0b6b15de0fbcf986f80ca Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 24 Nov 2022 13:13:01 +0100 Subject: [PATCH 10/18] remove test --- backend/src/graphql/resolver/TransactionResolver.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 86cdf5314..44ccc838a 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -291,6 +291,7 @@ describe('send coins', () => { await cleanDB() }) + /* describe('trying to send negative amount', () => { it('throws an error', async () => { expect( @@ -315,6 +316,7 @@ describe('send coins', () => { ) }) }) + */ describe('good transaction', () => { it('sends the coins', async () => { From 63e22eeab80b41ca4204f6604dfa5b6d30898b29 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 24 Nov 2022 16:30:55 +0100 Subject: [PATCH 11/18] fix(database): wrong balance and decay values --- .../0054-recalculate-balance-and-decay.ts | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 database/migrations/0054-recalculate-balance-and-decay.ts diff --git a/database/migrations/0054-recalculate-balance-and-decay.ts b/database/migrations/0054-recalculate-balance-and-decay.ts new file mode 100644 index 000000000..fe9896db1 --- /dev/null +++ b/database/migrations/0054-recalculate-balance-and-decay.ts @@ -0,0 +1,122 @@ +/* MIGRATION TO FIX WRONG BALANCE + * + * Due to a bug in the code + * the amount of a receive balance is substracted + * from the previous balance instead of added. + * + * Therefore all balance and decay fields must + * be recalculated + * + * WARNING: This Migration must be run in TZ=UTC + */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import Decimal from 'decimal.js-light' + +// Set precision value +Decimal.set({ + precision: 25, + rounding: Decimal.ROUND_HALF_UP, +}) + +const DECAY_START_TIME = new Date('2021-05-13 17:46:31') // GMT+0 + +interface Decay { + balance: Decimal + decay: Decimal | null + start: Date | null + end: Date | null + duration: number | null +} + +export enum TransactionTypeId { + CREATION = 1, + SEND = 2, + RECEIVE = 3, +} + +function decayFormula(value: Decimal, seconds: number): Decimal { + return value.mul(new Decimal('0.99999997803504048973201202316767079413460520837376').pow(seconds)) +} + +function calculateDecay( + amount: Decimal, + from: Date, + to: Date, + startBlock: Date = DECAY_START_TIME, +): Decay { + const fromMs = from.getTime() + const toMs = to.getTime() + const startBlockMs = startBlock.getTime() + + if (toMs < fromMs) { + throw new Error('to < from, reverse decay calculation is invalid') + } + + // Initialize with no decay + const decay: Decay = { + balance: amount, + decay: null, + start: null, + end: null, + duration: null, + } + + // decay started after end date; no decay + if (startBlockMs > toMs) { + return decay + } + // decay started before start date; decay for full duration + if (startBlockMs < fromMs) { + decay.start = from + decay.duration = (toMs - fromMs) / 1000 + } + // decay started between start and end date; decay from decay start till end date + else { + decay.start = startBlock + decay.duration = (toMs - startBlockMs) / 1000 + } + + decay.end = to + decay.balance = decayFormula(amount, decay.duration) + decay.decay = decay.balance.minus(amount) + return decay +} + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + // Find all users & loop over them + const users = await queryFn('SELECT user_id FROM transactions GROUP BY user_id;') + for (let u = 0; u < users.length; u++) { + // find all transactions for a user + const transactions = await queryFn( + `SELECT * FROM transactions WHERE user_id = ${users[u].user_id} ORDER BY balance_date ASC;`, + ) + let previous = null + let balance = new Decimal(0) + for (let t = 0; t < transactions.length; t++) { + const transaction = transactions[t] + + const decayStartDate = previous ? previous.balance_date : transaction.balance_date + const amount = new Decimal(transaction.amount) + const decay = calculateDecay(balance, decayStartDate, transaction.balance_date) + balance = decay.balance.add(amount) + + // Update + await queryFn(` + UPDATE transactions SET + balance = ${balance}, + decay = ${decay.decay ? decay.decay : 0} + WHERE id = ${transaction.id}; + `) + + // previous + previous = transaction + } + } +} + +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) {} From f2e88469325d1a188548d576367e923dbfa47d07 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 24 Nov 2022 16:37:32 +0100 Subject: [PATCH 12/18] rename migration file, set new db version in backend --- backend/src/config/index.ts | 2 +- ...lance-and-decay.ts => 0054-recalculate_balance_and_decay.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename database/migrations/{0054-recalculate-balance-and-decay.ts => 0054-recalculate_balance_and_decay.ts} (100%) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 26227b90d..a66ed9765 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0053-change_password_encryption', + DB_VERSION: '0054-recalculate_balance_and_decay', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/database/migrations/0054-recalculate-balance-and-decay.ts b/database/migrations/0054-recalculate_balance_and_decay.ts similarity index 100% rename from database/migrations/0054-recalculate-balance-and-decay.ts rename to database/migrations/0054-recalculate_balance_and_decay.ts From 0f71a486a5e86d0be0dcbdd25c6f0c49c758e9c4 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 25 Nov 2022 14:13:22 +0100 Subject: [PATCH 13/18] log affected accounts & some fixes --- database/log/.gitignore | 2 + .../0054-recalculate_balance_and_decay.ts | 52 +++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 database/log/.gitignore diff --git a/database/log/.gitignore b/database/log/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/database/log/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/database/migrations/0054-recalculate_balance_and_decay.ts b/database/migrations/0054-recalculate_balance_and_decay.ts index fe9896db1..6614b4a52 100644 --- a/database/migrations/0054-recalculate_balance_and_decay.ts +++ b/database/migrations/0054-recalculate_balance_and_decay.ts @@ -13,6 +13,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ +import fs from 'fs' import Decimal from 'decimal.js-light' // Set precision value @@ -86,30 +87,63 @@ function calculateDecay( } export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + // Write log file + const logFile = 'log/0054-recalculate_balance_and_decay.log.csv' + await fs.writeFile( + logFile, + `email;first_name;last_name;affected_transactions;new_balance;new_decay;old_balance;old_decay;delta;\n`, + (err) => { + if (err) throw err + }, + ) + // Find all users & loop over them const users = await queryFn('SELECT user_id FROM transactions GROUP BY user_id;') for (let u = 0; u < users.length; u++) { + const userId = users[u].user_id // find all transactions for a user const transactions = await queryFn( - `SELECT * FROM transactions WHERE user_id = ${users[u].user_id} ORDER BY balance_date ASC;`, + `SELECT *, CONVERT(balance, CHAR) as dec_balance, CONVERT(decay, CHAR) as dec_decay FROM transactions WHERE user_id = ${userId} ORDER BY balance_date ASC;`, ) + let previous = null + let affectedTransactions = 0 let balance = new Decimal(0) for (let t = 0; t < transactions.length; t++) { const transaction = transactions[t] - const decayStartDate = previous ? previous.balance_date : transaction.balance_date const amount = new Decimal(transaction.amount) const decay = calculateDecay(balance, decayStartDate, transaction.balance_date) balance = decay.balance.add(amount) - // Update - await queryFn(` - UPDATE transactions SET - balance = ${balance}, - decay = ${decay.decay ? decay.decay : 0} - WHERE id = ${transaction.id}; - `) + const userContact = await queryFn( + `SELECT email, first_name, last_name FROM users LEFT JOIN user_contacts ON users.email_id = user_contacts.id WHERE users.id = ${userId}`, + ) + const userEmail = userContact.length === 1 ? userContact[0].email : userId + const userFirstName = userContact.length === 1 ? userContact[0].first_name : '' + const userLastName = userContact.length === 1 ? userContact[0].last_name : '' + + // Update if needed + if (!balance.eq(transaction.dec_balance)) { + await queryFn(` + UPDATE transactions SET + balance = ${balance}, + decay = ${decay.decay ? decay.decay : 0} + WHERE id = ${transaction.id}; + `) + affectedTransactions++ + + // Log on last entry + if (t === transactions.length - 1) { + fs.appendFile( + logFile, + `${userEmail};${userFirstName};${userLastName};${affectedTransactions};${balance};${decay.decay ? decay.decay : 0};${transaction.dec_balance};${transaction.dec_decay};${balance.sub(transaction.dec_balance)};\n`, + (err) => { + if (err) throw err + }, + ) + } + } // previous previous = transaction From 1ea8eb1815b8da079da4b750e8c6519e4438c73b Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 25 Nov 2022 14:23:47 +0100 Subject: [PATCH 14/18] create log folder --- database/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/database/Dockerfile b/database/Dockerfile index 4069ffcd8..03c7d9a3b 100644 --- a/database/Dockerfile +++ b/database/Dockerfile @@ -100,6 +100,8 @@ COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json # Copy Mnemonic files COPY --from=build ${DOCKER_WORKDIR}/src/config/*.txt ./src/config/ +# Copy log folder +COPY --from=build ${DOCKER_WORKDIR}/log ./log # Copy run scripts run/ # COPY --from=build ${DOCKER_WORKDIR}/run ./run From bc0cbfe2f73d100807eaa6cc2b899a4c0f2eb891 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Fri, 25 Nov 2022 14:24:47 +0100 Subject: [PATCH 15/18] lint fix --- database/migrations/0054-recalculate_balance_and_decay.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/database/migrations/0054-recalculate_balance_and_decay.ts b/database/migrations/0054-recalculate_balance_and_decay.ts index 6614b4a52..516d0d1e3 100644 --- a/database/migrations/0054-recalculate_balance_and_decay.ts +++ b/database/migrations/0054-recalculate_balance_and_decay.ts @@ -105,7 +105,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis const transactions = await queryFn( `SELECT *, CONVERT(balance, CHAR) as dec_balance, CONVERT(decay, CHAR) as dec_decay FROM transactions WHERE user_id = ${userId} ORDER BY balance_date ASC;`, ) - + let previous = null let affectedTransactions = 0 let balance = new Decimal(0) @@ -137,7 +137,11 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis if (t === transactions.length - 1) { fs.appendFile( logFile, - `${userEmail};${userFirstName};${userLastName};${affectedTransactions};${balance};${decay.decay ? decay.decay : 0};${transaction.dec_balance};${transaction.dec_decay};${balance.sub(transaction.dec_balance)};\n`, + `${userEmail};${userFirstName};${userLastName};${affectedTransactions};${balance};${ + decay.decay ? decay.decay : 0 + };${transaction.dec_balance};${transaction.dec_decay};${balance.sub( + transaction.dec_balance, + )};\n`, (err) => { if (err) throw err }, From 62c48712a44f25b29b0768af5cb2a9ceeaed23f4 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 25 Nov 2022 14:32:12 +0100 Subject: [PATCH 16/18] fix misspelled text --- backend/src/graphql/resolver/TransactionResolver.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 44ccc838a..f4315d359 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -326,7 +326,7 @@ describe('send coins', () => { variables: { email: 'peter@lustig.de', amount: 50, - memo: 'unrepeateable memo', + memo: 'unrepeatable memo', }, }), ).toEqual( @@ -342,7 +342,7 @@ describe('send coins', () => { // Find the exact transaction (sent one is the one with user[1] as user) const transaction = await Transaction.find({ userId: user[1].id, - memo: 'unrepeateable memo', + memo: 'unrepeatable memo', }) expect(EventProtocol.find()).resolves.toContainEqual( @@ -359,7 +359,7 @@ describe('send coins', () => { // Find the exact transaction (received one is the one with user[0] as user) const transaction = await Transaction.find({ userId: user[0].id, - memo: 'unrepeateable memo', + memo: 'unrepeatable memo', }) expect(EventProtocol.find()).resolves.toContainEqual( From 9253706513dbfd49e688915d5da01f138e131c31 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 25 Nov 2022 14:32:23 +0100 Subject: [PATCH 17/18] remove comment --- backend/src/util/validate.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts index 8d1c90ca4..edd8d55f6 100644 --- a/backend/src/util/validate.ts +++ b/backend/src/util/validate.ts @@ -29,7 +29,6 @@ async function calculateBalance( const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time) - // TODO why we have to use toString() here? const balance = decay.balance.add(amount.toString()) const transactionLinkRepository = getCustomRepository(TransactionLinkRepository) const { sumHoldAvailableAmount } = await transactionLinkRepository.summary(userId, time) From 2caf0ee664c57bf57fdf1957fdb00fb1b6ba2436 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Sat, 26 Nov 2022 01:14:24 +0100 Subject: [PATCH 18/18] v1.15.0 --- CHANGELOG.md | 13 +++++++++++++ admin/package.json | 2 +- backend/package.json | 2 +- database/package.json | 2 +- frontend/package.json | 2 +- package.json | 2 +- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ce354b1e..26b71ea03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,21 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.15.0](https://github.com/gradido/gradido/compare/1.14.1...1.15.0) + +- fix(database): wrong balance and decay values [`#2423`](https://github.com/gradido/gradido/pull/2423) +- fix(backend): wrong balance after transaction receive [`#2422`](https://github.com/gradido/gradido/pull/2422) +- feat(other): feature gradido roadmap [`#2301`](https://github.com/gradido/gradido/pull/2301) +- refactor(backend): new password encryption implementation [`#2353`](https://github.com/gradido/gradido/pull/2353) +- refactor(admin): statistics in a table and on separate page in admin area [`#2399`](https://github.com/gradido/gradido/pull/2399) +- feat(backend): 🍰 Email Templates [`#2163`](https://github.com/gradido/gradido/pull/2163) +- fix(backend): timezone problems [`#2393`](https://github.com/gradido/gradido/pull/2393) + #### [1.14.1](https://github.com/gradido/gradido/compare/1.14.0...1.14.1) +> 14 November 2022 + +- chore(release): version 1.14.1 - hotfix [`#2391`](https://github.com/gradido/gradido/pull/2391) - fix(frontend): load contributionMessages is fixed [`#2390`](https://github.com/gradido/gradido/pull/2390) #### [1.14.0](https://github.com/gradido/gradido/compare/1.13.3...1.14.0) diff --git a/admin/package.json b/admin/package.json index 7f0e7ffd5..75800a526 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.14.1", + "version": "1.15.0", "license": "Apache-2.0", "private": false, "scripts": { diff --git a/backend/package.json b/backend/package.json index 3e26225bf..25774fc7d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "gradido-backend", - "version": "1.14.1", + "version": "1.15.0", "description": "Gradido unified backend providing an API-Service for Gradido Transactions", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/backend", diff --git a/database/package.json b/database/package.json index 6216a25fb..abc7789c4 100644 --- a/database/package.json +++ b/database/package.json @@ -1,6 +1,6 @@ { "name": "gradido-database", - "version": "1.14.1", + "version": "1.15.0", "description": "Gradido Database Tool to execute database migrations", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/database", diff --git a/frontend/package.json b/frontend/package.json index cfc12630e..6f1474521 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap-vue-gradido-wallet", - "version": "1.14.1", + "version": "1.15.0", "private": true, "scripts": { "start": "node run/server.js", diff --git a/package.json b/package.json index 72efee984..22f444155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gradido", - "version": "1.14.1", + "version": "1.15.0", "description": "Gradido", "main": "index.js", "repository": "git@github.com:gradido/gradido.git",