mirror of
https://github.com/IT4Change/gradido.git
synced 2026-04-06 01:25:28 +00:00
Merge branch 'master' into refactor_drizzle_orm_for_project_branding
This commit is contained in:
commit
db9c380bec
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -1,7 +1,7 @@
|
||||
name: gradido publish CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
@ -10,7 +10,7 @@ jobs:
|
||||
# JOB: DOCKER BUILD PRODUCTION FRONTEND ######################################
|
||||
##############################################################################
|
||||
build_production_frontend:
|
||||
if: startsWith(github.event.head_commit.message, 'chore(release):')
|
||||
if: startsWith(github.event.pull_request.title, 'chore(release):')
|
||||
name: Docker Build Production - Frontend
|
||||
runs-on: ubuntu-latest
|
||||
#needs: [nothing]
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@ -4,8 +4,26 @@ 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).
|
||||
|
||||
#### [v2.7.4](https://github.com/gradido/gradido/compare/v2.7.3...v2.7.4)
|
||||
|
||||
- feat(workflow): update .env.dist in deploy and make install script more idempotent [`#3593`](https://github.com/gradido/gradido/pull/3593)
|
||||
- feat(admin): load moderator names for contribution list [`#3599`](https://github.com/gradido/gradido/pull/3599)
|
||||
- fix(other): email mime types [`#3601`](https://github.com/gradido/gradido/pull/3601)
|
||||
- fix(database): use connection pool for drizzle orm [`#3600`](https://github.com/gradido/gradido/pull/3600)
|
||||
- feat(admin): make deleted contributions better recognisable with Deuteranopie [`#3597`](https://github.com/gradido/gradido/pull/3597)
|
||||
- fix(workflow): rewrite sort.sh as sortLocales.ts [`#3598`](https://github.com/gradido/gradido/pull/3598)
|
||||
- refactor(database): add drizzleOrm, use it in openaiThreads [`#3595`](https://github.com/gradido/gradido/pull/3595)
|
||||
- fix(other): fix biome config [`#3592`](https://github.com/gradido/gradido/pull/3592)
|
||||
- feat(other): reduce github worker count [`#3591`](https://github.com/gradido/gradido/pull/3591)
|
||||
- fix(other): removed phantom line, aligned env and compose [`#3570`](https://github.com/gradido/gradido/pull/3570)
|
||||
- feat(dlt): migrate database transaction to dlt transactions [`#3571`](https://github.com/gradido/gradido/pull/3571)
|
||||
- feat(dlt): add inspector as submodule, update nginx config to serve inspector and gradido node [`#3566`](https://github.com/gradido/gradido/pull/3566)
|
||||
- chore(release): v2.7.3 [`#3590`](https://github.com/gradido/gradido/pull/3590)
|
||||
|
||||
#### [v2.7.3](https://github.com/gradido/gradido/compare/v2.7.2...v2.7.3)
|
||||
|
||||
> 4 December 2025
|
||||
|
||||
- feat(admin): show user registered at in admin [`#3589`](https://github.com/gradido/gradido/pull/3589)
|
||||
- feat(backend): 3573 feature introduce distributed semaphore base on redis [`#3580`](https://github.com/gradido/gradido/pull/3580)
|
||||
- fix(workflow): make deployment install script more robust [`#3588`](https://github.com/gradido/gradido/pull/3588)
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
CONFIG_VERSION=$ADMIN_CONFIG_VERSION
|
||||
|
||||
COMMUNITY_HOST=$COMMUNITY_HOST
|
||||
URL_PROTOCOL=$URL_PROTOCOL
|
||||
WALLET_AUTH_PATH=$WALLET_AUTH_PATH
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administration Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Gradido Academy - https://www.gradido.net",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -18,8 +18,8 @@
|
||||
"test:coverage": "cross-env TZ=UTC vitest run --coverage",
|
||||
"test:debug": "cross-env TZ=UTC node --inspect-brk ./node_modules/vitest/vitest.mjs",
|
||||
"test:watch": "cross-env TZ=UTC vitest",
|
||||
"locales": "scripts/sort.sh",
|
||||
"locales:fix": "scripts/sort.sh --fix",
|
||||
"locales": "bun scripts/sortLocales.ts",
|
||||
"locales:fix": "bun scripts/sortLocales.ts --fix",
|
||||
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
51
admin/scripts/sortLocales.ts
Normal file
51
admin/scripts/sortLocales.ts
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bun
|
||||
import { readdir, readFile, writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
|
||||
const ROOT_DIR = join(import.meta.dir, '..')
|
||||
const LOCALES_DIR = join(ROOT_DIR, 'src', 'locales')
|
||||
|
||||
const FIX = process.argv.includes('--fix')
|
||||
|
||||
function sortObject(value: any): any {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(sortObject)
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.keys(value)
|
||||
.sort()
|
||||
.reduce<Record<string, any>>((acc, key) => {
|
||||
acc[key] = sortObject(value[key])
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
let exitCode = 0
|
||||
|
||||
const files = (await readdir(LOCALES_DIR))
|
||||
.filter(f => f.endsWith('.json'))
|
||||
|
||||
for (const file of files) {
|
||||
const path = join(LOCALES_DIR, file)
|
||||
|
||||
const originalText = await readFile(path, 'utf8')
|
||||
const originalJson = JSON.parse(originalText)
|
||||
|
||||
const sortedJson = sortObject(originalJson)
|
||||
const sortedText = JSON.stringify(sortedJson, null, 2) + '\n'
|
||||
|
||||
if (originalText !== sortedText) {
|
||||
if (FIX) {
|
||||
await writeFile(path, sortedText)
|
||||
} else {
|
||||
console.error(`${file} is not sorted by keys`)
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(exitCode)
|
||||
@ -6,7 +6,7 @@
|
||||
:icon="type === 'PageCreationConfirm' ? 'x' : 'eye-slash-fill'"
|
||||
aria-label="Help"
|
||||
></b-icon>
|
||||
{{ $t('hide_details') }} {{ row.item.user.firstName }} {{ row.item.user.lastName }}
|
||||
{{ $t('hide_details') }}
|
||||
</b-button>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
@ -14,7 +14,13 @@
|
||||
<IBiBellFill v-else-if="row.item.contributionStatus === 'PENDING'" />
|
||||
<IBiCheck v-else-if="row.item.contributionStatus === 'CONFIRMED'" />
|
||||
<IBiXCircle v-else-if="row.item.contributionStatus === 'DENIED'" />
|
||||
<IBiTrash v-else-if="row.item.contributionStatus === 'DELETED'" />
|
||||
<IBiTrash
|
||||
v-else-if="row.item.contributionStatus === 'DELETED'"
|
||||
class="p-1"
|
||||
width="24"
|
||||
height="24"
|
||||
style="background-color: #dc3545; color: white"
|
||||
/>
|
||||
</template>
|
||||
<template #cell(bookmark)="row">
|
||||
<div v-if="!myself(row.item)">
|
||||
@ -28,11 +34,20 @@
|
||||
</BButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(name)="row">
|
||||
<span v-if="row.item.user">
|
||||
{{ row.item.user.firstName }} {{ row.item.user.lastName }}
|
||||
<small v-if="row.item.user.alias">
|
||||
<hr />
|
||||
{{ row.item.user.alias }}
|
||||
</small>
|
||||
</span>
|
||||
</template>
|
||||
<template #cell(memo)="row">
|
||||
{{ row.value }}
|
||||
<small v-if="row.item.updatedBy > 0">
|
||||
<small v-if="isAddCommentToMemo(row.item)" class="no-select">
|
||||
<hr />
|
||||
{{ $t('moderator.memo-modified') }}
|
||||
{{ getMemoComment(row.item) }}
|
||||
</small>
|
||||
</template>
|
||||
<template #cell(editCreation)="row">
|
||||
@ -134,6 +149,7 @@
|
||||
import RowDetails from '../RowDetails'
|
||||
import EditCreationFormular from '../EditCreationFormular'
|
||||
import ContributionMessagesList from '../ContributionMessages/ContributionMessagesList'
|
||||
import { useDateFormatter } from '@/composables/useDateFormatter'
|
||||
|
||||
const iconMap = {
|
||||
IN_PROGRESS: 'question-square',
|
||||
@ -189,6 +205,7 @@ export default {
|
||||
this.removeClipboardListener()
|
||||
},
|
||||
methods: {
|
||||
...useDateFormatter(),
|
||||
myself(item) {
|
||||
return item.userId === this.$store.state.moderator.id
|
||||
},
|
||||
@ -229,6 +246,36 @@ export default {
|
||||
this.creationUserData = row.item
|
||||
}
|
||||
},
|
||||
isAddCommentToMemo(item) {
|
||||
return item.closedBy > 0 || item.moderatorId > 0 || item.updatedBy > 0
|
||||
},
|
||||
getMemoComment(item) {
|
||||
let comment = ''
|
||||
if (item.closedBy > 0) {
|
||||
if (item.contributionStatus === 'CONFIRMED') {
|
||||
comment = this.$t('contribution.confirmedBy', { name: item.closedByUserName })
|
||||
} else if (item.contributionStatus === 'DENIED') {
|
||||
comment = this.$t('contribution.deniedBy', { name: item.closedByUserName })
|
||||
} else if (item.contributionStatus === 'DELETED') {
|
||||
comment = this.$t('contribution.deletedBy', { name: item.closedByUserName })
|
||||
}
|
||||
}
|
||||
|
||||
if (item.updatedBy > 0) {
|
||||
if (comment.length) {
|
||||
comment += ' | '
|
||||
}
|
||||
comment += this.$t('moderator.memo-modified', { name: item.updatedByUserName })
|
||||
}
|
||||
|
||||
if (item.moderatorId > 0) {
|
||||
if (comment.length) {
|
||||
comment += ' | '
|
||||
}
|
||||
comment += this.$t('contribution.createdBy', { name: item.moderatorUserName })
|
||||
}
|
||||
return comment
|
||||
},
|
||||
addClipboardListener() {
|
||||
document.addEventListener('copy', this.handleCopy)
|
||||
},
|
||||
@ -254,4 +301,9 @@ export default {
|
||||
background-color: #e1a908;
|
||||
border-color: #e1a908;
|
||||
}
|
||||
|
||||
.table-danger {
|
||||
--bs-table-bg: #e78d8d;
|
||||
--bs-table-striped-bg: #e57373;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
export const useDateFormatter = () => {
|
||||
const formatDateFromDateTime = (datetimeString) => {
|
||||
if (!datetimeString || !datetimeString?.includes('T')) return datetimeString
|
||||
if (!datetimeString || !datetimeString?.includes('T')) {
|
||||
return datetimeString
|
||||
}
|
||||
return datetimeString.split('T')[0]
|
||||
}
|
||||
const formatDateOrDash = (value) => (value ? new Date(value).toLocaleDateString() : '—')
|
||||
|
||||
return {
|
||||
formatDateFromDateTime,
|
||||
formatDateOrDash,
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,19 +19,18 @@ query adminListContributions(
|
||||
}
|
||||
amount
|
||||
memo
|
||||
createdAt
|
||||
closedAt
|
||||
closedBy
|
||||
closedByUserName
|
||||
contributionDate
|
||||
confirmedAt
|
||||
confirmedBy
|
||||
createdAt
|
||||
updatedAt
|
||||
updatedBy
|
||||
updatedBy
|
||||
updatedByUserName
|
||||
contributionStatus
|
||||
messagesCount
|
||||
deniedAt
|
||||
deniedBy
|
||||
deletedAt
|
||||
deletedBy
|
||||
moderatorId
|
||||
moderatorUserName
|
||||
userId
|
||||
resubmissionAt
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
"actions": "Aktionen",
|
||||
"ai": {
|
||||
"chat": "Chat",
|
||||
"chat-open": "Chat öffnen",
|
||||
"chat-clear": "Chat-Verlauf löschen",
|
||||
"chat-open": "Chat öffnen",
|
||||
"chat-placeholder": "Schreibe eine Nachricht...",
|
||||
"chat-placeholder-loading": "Warte, ich denke nach...",
|
||||
"chat-thread-deleted": "Chatverlauf gelöscht",
|
||||
@ -16,6 +16,12 @@
|
||||
"back": "zurück",
|
||||
"change_user_role": "Nutzerrolle ändern",
|
||||
"close": "Schließen",
|
||||
"contribution": {
|
||||
"confirmedBy": "Bestätigt von {name}.",
|
||||
"createdBy": "Erstellt von {name}.",
|
||||
"deletedBy": "Gelöscht von {name}.",
|
||||
"deniedBy": "Abgelehnt von {name}."
|
||||
},
|
||||
"contributionLink": {
|
||||
"amount": "Betrag",
|
||||
"changeSaved": "Änderungen gespeichert",
|
||||
@ -23,8 +29,8 @@
|
||||
"contributionLinks": "Beitragslinks",
|
||||
"create": "Anlegen",
|
||||
"cycle": "Zyklus",
|
||||
"deleted": "Automatische Schöpfung gelöscht!",
|
||||
"deleteNow": "Automatische Creations '{name}' wirklich löschen?",
|
||||
"deleted": "Automatische Schöpfung gelöscht!",
|
||||
"maxPerCycle": "Wiederholungen",
|
||||
"memo": "Nachricht",
|
||||
"name": "Name",
|
||||
@ -44,11 +50,12 @@
|
||||
"validTo": "Enddatum"
|
||||
},
|
||||
"contributionMessagesForm": {
|
||||
"resubmissionDateInPast": "Wiedervorlage Datum befindet sich in der Vergangenheit!",
|
||||
"hasRegisteredAt": "hat sich am {createdAt} registriert."
|
||||
"hasRegisteredAt": "hat sich am {createdAt} registriert.",
|
||||
"resubmissionDateInPast": "Wiedervorlage Datum befindet sich in der Vergangenheit!"
|
||||
},
|
||||
"contributions": {
|
||||
"all": "Alle",
|
||||
"closed": "Geschlossen",
|
||||
"confirms": "Bestätigt",
|
||||
"deleted": "Gelöscht",
|
||||
"denied": "Abgelehnt",
|
||||
@ -96,16 +103,16 @@
|
||||
"coordinates": "Koordinaten:",
|
||||
"createdAt": "Erstellt am",
|
||||
"gmsApiKey": "GMS API Key:",
|
||||
"hieroTopicId": "Hiero Topic ID:",
|
||||
"toast_gmsApiKeyAndLocationUpdated": "Der GMS Api Key und die Location wurden erfolgreich aktualisiert!",
|
||||
"toast_gmsApiKeyUpdated": "Der GMS Api Key wurde erfolgreich aktualisiert!",
|
||||
"toast_gmsLocationUpdated": "Die GMS Location wurde erfolgreich aktualisiert!",
|
||||
"toast_hieroTopicIdUpdated": "Die Hiero Topic ID wurde erfolgreich aktualisiert!",
|
||||
"gradidoInstances": "Gradido Instanzen",
|
||||
"hieroTopicId": "Hiero Topic ID:",
|
||||
"lastAnnouncedAt": "letzte Bekanntgabe",
|
||||
"lastErrorAt": "Letzer Fehler am",
|
||||
"name": "Name",
|
||||
"publicKey": "PublicKey:",
|
||||
"toast_gmsApiKeyAndLocationUpdated": "Der GMS Api Key und die Location wurden erfolgreich aktualisiert!",
|
||||
"toast_gmsApiKeyUpdated": "Der GMS Api Key wurde erfolgreich aktualisiert!",
|
||||
"toast_gmsLocationUpdated": "Die GMS Location wurde erfolgreich aktualisiert!",
|
||||
"toast_hieroTopicIdUpdated": "Die Hiero Topic ID wurde erfolgreich aktualisiert!",
|
||||
"url": "Url",
|
||||
"verified": "Verifiziert",
|
||||
"verifiedAt": "Verifiziert am"
|
||||
@ -134,8 +141,8 @@
|
||||
}
|
||||
},
|
||||
"goTo": {
|
||||
"userSearch": "Zur Nutzersuche gehen",
|
||||
"humhubProfile": "Zum Humhub Profil gehen"
|
||||
"humhubProfile": "Zum Humhub Profil gehen",
|
||||
"userSearch": "Zur Nutzersuche gehen"
|
||||
},
|
||||
"help": {
|
||||
"help": "Hilfe",
|
||||
@ -165,16 +172,16 @@
|
||||
},
|
||||
"moderator": {
|
||||
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
|
||||
"show-submission-form": "warten auf Erinnerung?",
|
||||
"memo": "Text ändern",
|
||||
"memo-modified": "Text von {name} bearbeitet.",
|
||||
"memo-tooltip": "Den Beitragstext bearbeiten",
|
||||
"message": "Nachricht",
|
||||
"message-tooltip": "Nachricht an Benutzer schreiben",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Notiz",
|
||||
"notice-tooltip": "Die Notiz ist nur für Moderatoren sichtbar",
|
||||
"memo": "Text ändern",
|
||||
"memo-tooltip": "Den Beitragstext bearbeiten",
|
||||
"memo-modified": "Text vom Moderator bearbeitet.",
|
||||
"message": "Nachricht",
|
||||
"message-tooltip": "Nachricht an Benutzer schreiben",
|
||||
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!"
|
||||
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!",
|
||||
"show-submission-form": "warten auf Erinnerung?"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
@ -233,14 +240,14 @@
|
||||
"chosenSpace": "Gewählter Space: {space}",
|
||||
"created": "Neuer Projekt Branding Eintrag wurde erstellt.",
|
||||
"error": "Fehler beim Erstellen des Projekt Branding Eintrags: {message}",
|
||||
"newUserToSpace": "Benutzer hinzufügen?",
|
||||
"newUserToSpaceTooltip": "Neue Benutzer automatisch zum Space hinzufügen, falls Space vorhanden",
|
||||
"noAccessRightSpace": "Gewählter Space: {spaceId} (Keine Zugriffsrechte)",
|
||||
"openSpaceInHumhub": "In Humhub öffnen",
|
||||
"spaceId": "Humhub Space ID",
|
||||
"selectSpace": "Humhub Space auswählen",
|
||||
"spaceId": "Humhub Space ID",
|
||||
"title": "Projekt Brandings",
|
||||
"updated": "Projekt Branding Eintrag wurde aktualisiert.",
|
||||
"newUserToSpace": "Benutzer hinzufügen?",
|
||||
"newUserToSpaceTooltip": "Neue Benutzer automatisch zum Space hinzufügen, falls Space vorhanden"
|
||||
"updated": "Projekt Branding Eintrag wurde aktualisiert."
|
||||
},
|
||||
"redeemed": "eingelöst",
|
||||
"registered": "Registriert",
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
"actions": "Actions",
|
||||
"ai": {
|
||||
"chat": "Chat",
|
||||
"chat-open": "Open chat",
|
||||
"chat-clear": "Clear chat",
|
||||
"chat-open": "Open chat",
|
||||
"chat-placeholder": "Type your message here...",
|
||||
"chat-placeholder-loading": "Wait, I think...",
|
||||
"chat-thread-deleted": "Chat thread has been deleted",
|
||||
@ -16,6 +16,12 @@
|
||||
"back": "back",
|
||||
"change_user_role": "Change user role",
|
||||
"close": "Close",
|
||||
"contribution": {
|
||||
"confirmedBy": "Confirmed by {name}.",
|
||||
"createdBy": "Created by {name}.",
|
||||
"deletedBy": "Deleted by {name}.",
|
||||
"deniedBy": "Rejected by {name}."
|
||||
},
|
||||
"contributionLink": {
|
||||
"amount": "Amount",
|
||||
"changeSaved": "Changes saved",
|
||||
@ -23,8 +29,8 @@
|
||||
"contributionLinks": "Contribution Links",
|
||||
"create": "Create",
|
||||
"cycle": "Cycle",
|
||||
"deleted": "Automatic creation deleted!",
|
||||
"deleteNow": "Do you really delete automatic creations '{name}'?",
|
||||
"deleted": "Automatic creation deleted!",
|
||||
"maxPerCycle": "Repetition",
|
||||
"memo": "Memo",
|
||||
"name": "Name",
|
||||
@ -44,11 +50,12 @@
|
||||
"validTo": "End-Date"
|
||||
},
|
||||
"contributionMessagesForm": {
|
||||
"resubmissionDateInPast": "Resubmission date is in the past!",
|
||||
"hasRegisteredAt": "registered on {createdAt}."
|
||||
"hasRegisteredAt": "registered on {createdAt}.",
|
||||
"resubmissionDateInPast": "Resubmission date is in the past!"
|
||||
},
|
||||
"contributions": {
|
||||
"all": "All",
|
||||
"closed": "Closed",
|
||||
"confirms": "Confirmed",
|
||||
"deleted": "Deleted",
|
||||
"denied": "Rejected",
|
||||
@ -96,16 +103,16 @@
|
||||
"coordinates": "Coordinates:",
|
||||
"createdAt": "Created At ",
|
||||
"gmsApiKey": "GMS API Key:",
|
||||
"hieroTopicId": "Hiero Topic ID:",
|
||||
"toast_gmsApiKeyAndLocationUpdated": "The GMS Api Key and the location have been successfully updated!",
|
||||
"toast_gmsApiKeyUpdated": "The GMS Api Key has been successfully updated!",
|
||||
"toast_gmsLocationUpdated": "The GMS location has been successfully updated!",
|
||||
"toast_hieroTopicIdUpdated": "The Hiero Topic ID has been successfully updated!",
|
||||
"gradidoInstances": "Gradido Instances",
|
||||
"hieroTopicId": "Hiero Topic ID:",
|
||||
"lastAnnouncedAt": "Last Announced",
|
||||
"lastErrorAt": "last error at",
|
||||
"name": "Name",
|
||||
"publicKey": "PublicKey:",
|
||||
"toast_gmsApiKeyAndLocationUpdated": "The GMS Api Key and the location have been successfully updated!",
|
||||
"toast_gmsApiKeyUpdated": "The GMS Api Key has been successfully updated!",
|
||||
"toast_gmsLocationUpdated": "The GMS location has been successfully updated!",
|
||||
"toast_hieroTopicIdUpdated": "The Hiero Topic ID has been successfully updated!",
|
||||
"url": "Url",
|
||||
"verified": "Verified",
|
||||
"verifiedAt": "Verified at"
|
||||
@ -127,15 +134,15 @@
|
||||
},
|
||||
"geo-coordinates": {
|
||||
"both-or-none": "Please enter both or none!",
|
||||
"label": "geo-coordinates",
|
||||
"format": "{latitude}, {longitude}",
|
||||
"label": "geo-coordinates",
|
||||
"latitude-longitude-smart": {
|
||||
"describe": "Automatically splits coordinates in the format 'latitude, longitude'. Simply enter your coordinates from Google Maps here, for example: 49.28187664243721, 9.740672183943639."
|
||||
}
|
||||
},
|
||||
"goTo": {
|
||||
"userSearch": "Go to user search",
|
||||
"humhubProfile": "Go to Humhub profile"
|
||||
"humhubProfile": "Go to Humhub profile",
|
||||
"userSearch": "Go to user search"
|
||||
},
|
||||
"help": {
|
||||
"help": "Help",
|
||||
@ -165,16 +172,16 @@
|
||||
},
|
||||
"moderator": {
|
||||
"history": "The data has been changed. This is the old data.",
|
||||
"show-submission-form": "wait for reminder?",
|
||||
"memo": "Edit text",
|
||||
"memo-modified": "Text edited by {name}",
|
||||
"memo-tooltip": "Edit the text of the contribution",
|
||||
"message": "Message",
|
||||
"message-tooltip": "Write message to user",
|
||||
"moderator": "Moderator",
|
||||
"notice": "Note",
|
||||
"notice-tooltip": "The note is only visible to moderators",
|
||||
"memo": "Edit text",
|
||||
"memo-tooltip": "Edit the text of the contribution",
|
||||
"memo-modified": "Text edited by moderator",
|
||||
"message": "Message",
|
||||
"message-tooltip": "Write message to user",
|
||||
"request": "This message is only visible to the moderators!"
|
||||
"request": "This message is only visible to the moderators!",
|
||||
"show-submission-form": "wait for reminder?"
|
||||
},
|
||||
"name": "Name",
|
||||
"navbar": {
|
||||
@ -233,14 +240,14 @@
|
||||
"chosenSpace": "Choosen Humhub Space: {space}",
|
||||
"created": "New project branding entry has been created.",
|
||||
"error": "Error when creating the project branding entry: {message}",
|
||||
"newUserToSpace": "Add user?",
|
||||
"newUserToSpaceTooltip": "The hours should contain a maximum of two decimal places",
|
||||
"noAccessRightSpace": "Selected space: {spaceId} (No access rights)",
|
||||
"openSpaceInHumhub": "Open in Humhub",
|
||||
"spaceId": "Humhub Space ID",
|
||||
"selectSpace": "Select Humhub Space",
|
||||
"spaceId": "Humhub Space ID",
|
||||
"title": "Project Branding",
|
||||
"updated": "Project branding entry has been updated.",
|
||||
"newUserToSpace": "Add user?",
|
||||
"newUserToSpaceTooltip": "The hours should contain a maximum of two decimal places"
|
||||
"updated": "Project branding entry has been updated."
|
||||
},
|
||||
"redeemed": "redeemed",
|
||||
"registered": "Registered",
|
||||
|
||||
@ -108,6 +108,7 @@ import { confirmContribution } from '../graphql/confirmContribution'
|
||||
import { denyContribution } from '../graphql/denyContribution'
|
||||
import { getContribution } from '../graphql/getContribution'
|
||||
import { useAppToast } from '@/composables/useToast'
|
||||
import { useDateFormatter } from '@/composables/useDateFormatter'
|
||||
import CONFIG from '@/config'
|
||||
|
||||
const FILTER_TAB_MAP = [
|
||||
@ -134,9 +135,10 @@ const query = ref('')
|
||||
const noHashtag = ref(null)
|
||||
const hideResubmissionModel = ref(true)
|
||||
|
||||
const formatDateOrDash = (value) => (value ? new Date(value).toLocaleDateString() : '—')
|
||||
const { formatDateOrDash } = useDateFormatter()
|
||||
|
||||
const baseFields = {
|
||||
name: { key: 'name', label: t('name'), class: 'no-select' },
|
||||
firstName: { key: 'user.firstName', label: t('firstname'), class: 'no-select' },
|
||||
lastName: { key: 'user.lastName', label: t('lastname'), class: 'no-select' },
|
||||
amount: { key: 'amount', label: t('creation'), formatter: (value) => value + ' GDD' },
|
||||
@ -153,13 +155,12 @@ const baseFields = {
|
||||
class: 'no-select',
|
||||
formatter: formatDateOrDash,
|
||||
},
|
||||
confirmedAt: {
|
||||
key: 'confirmedAt',
|
||||
label: t('contributions.confirms'),
|
||||
closedAt: {
|
||||
key: 'closedAt',
|
||||
label: t('contributions.closed'),
|
||||
class: 'no-select',
|
||||
formatter: formatDateOrDash,
|
||||
},
|
||||
confirmedBy: { key: 'confirmedBy', label: t('moderator.moderator'), class: 'no-select' },
|
||||
}
|
||||
|
||||
const fields = computed(
|
||||
@ -169,70 +170,52 @@ const fields = computed(
|
||||
[
|
||||
{ key: 'bookmark', label: t('delete') },
|
||||
{ key: 'deny', label: t('deny') },
|
||||
baseFields.firstName,
|
||||
baseFields.lastName,
|
||||
baseFields.name,
|
||||
baseFields.amount,
|
||||
baseFields.memo,
|
||||
baseFields.contributionDate,
|
||||
{ key: 'moderatorId', label: t('moderator.moderator'), class: 'no-select' },
|
||||
{ key: 'editCreation', label: t('details') },
|
||||
{ key: 'confirm', label: t('save') },
|
||||
],
|
||||
// confirmed contributions
|
||||
[
|
||||
baseFields.firstName,
|
||||
baseFields.lastName,
|
||||
baseFields.name,
|
||||
baseFields.amount,
|
||||
baseFields.memo,
|
||||
baseFields.contributionDate,
|
||||
baseFields.createdAt,
|
||||
baseFields.confirmedAt,
|
||||
baseFields.confirmedBy,
|
||||
baseFields.closedAt,
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
// denied contributions
|
||||
[
|
||||
baseFields.firstName,
|
||||
baseFields.lastName,
|
||||
baseFields.name,
|
||||
baseFields.amount,
|
||||
baseFields.memo,
|
||||
baseFields.contributionDate,
|
||||
baseFields.createdAt,
|
||||
{
|
||||
key: 'deniedAt',
|
||||
label: t('contributions.denied'),
|
||||
formatter: formatDateOrDash,
|
||||
},
|
||||
{ key: 'deniedBy', label: t('moderator.moderator') },
|
||||
baseFields.closedAt,
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
// deleted contributions
|
||||
[
|
||||
baseFields.firstName,
|
||||
baseFields.lastName,
|
||||
baseFields.name,
|
||||
baseFields.amount,
|
||||
baseFields.memo,
|
||||
baseFields.contributionDate,
|
||||
baseFields.createdAt,
|
||||
{
|
||||
key: 'deletedAt',
|
||||
label: t('contributions.deleted'),
|
||||
formatter: formatDateOrDash,
|
||||
},
|
||||
{ key: 'deletedBy', label: t('moderator.moderator') },
|
||||
baseFields.closedAt,
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
// all contributions
|
||||
[
|
||||
{ key: 'contributionStatus', label: t('status') },
|
||||
baseFields.firstName,
|
||||
baseFields.lastName,
|
||||
baseFields.name,
|
||||
baseFields.amount,
|
||||
baseFields.memo,
|
||||
baseFields.contributionDate,
|
||||
baseFields.createdAt,
|
||||
baseFields.confirmedAt,
|
||||
baseFields.confirmedBy,
|
||||
baseFields.closedAt,
|
||||
{ key: 'chatCreation', label: t('details') },
|
||||
],
|
||||
][tabIndex.value],
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
|
||||
|
||||
# Server
|
||||
JWT_SECRET=$JWT_SECRET
|
||||
JWT_EXPIRES_IN=$JWT_EXPIRES_IN
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"private": false,
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||
import { Contribution as DbContribution } from 'database'
|
||||
import { Field, Int, ObjectType } from 'type-graphql'
|
||||
|
||||
import { UnconfirmedContribution } from './UnconfirmedContribution'
|
||||
|
||||
@ObjectType()
|
||||
@ -8,6 +8,7 @@ export class Contribution extends UnconfirmedContribution {
|
||||
constructor(dbContribution: DbContribution) {
|
||||
super(dbContribution)
|
||||
this.createdAt = dbContribution.createdAt
|
||||
this.moderatorId = dbContribution.moderatorId
|
||||
this.confirmedAt = dbContribution.confirmedAt
|
||||
this.confirmedBy = dbContribution.confirmedBy
|
||||
this.contributionDate = dbContribution.contributionDate
|
||||
@ -19,11 +20,36 @@ export class Contribution extends UnconfirmedContribution {
|
||||
this.updatedAt = dbContribution.updatedAt
|
||||
this.updatedBy = dbContribution.updatedBy
|
||||
this.resubmissionAt = dbContribution.resubmissionAt
|
||||
if (ContributionStatus.CONFIRMED === dbContribution.contributionStatus) {
|
||||
this.closedAt = dbContribution.confirmedAt
|
||||
this.closedBy = dbContribution.confirmedBy
|
||||
} else if (ContributionStatus.DELETED === dbContribution.contributionStatus) {
|
||||
this.closedAt = dbContribution.deletedAt
|
||||
this.closedBy = dbContribution.deletedBy
|
||||
} else if (ContributionStatus.DENIED === dbContribution.contributionStatus) {
|
||||
this.closedAt = dbContribution.deniedAt
|
||||
this.closedBy = dbContribution.deniedBy
|
||||
}
|
||||
}
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
closedAt?: Date | null
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
closedBy?: number | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
closedByUserName?: string | null
|
||||
|
||||
@Field(() => Date)
|
||||
createdAt: Date
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
moderatorId: number | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
moderatorUserName?: string | null
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
confirmedAt: Date | null
|
||||
|
||||
@ -48,6 +74,9 @@ export class Contribution extends UnconfirmedContribution {
|
||||
@Field(() => Int, { nullable: true })
|
||||
updatedBy: number | null
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
updatedByUserName?: string | null
|
||||
|
||||
@Field(() => Date)
|
||||
contributionDate: Date
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
Contribution as DbContribution,
|
||||
Transaction as DbTransaction,
|
||||
User as DbUser,
|
||||
findUserNamesByIds,
|
||||
getLastTransaction,
|
||||
UserContact,
|
||||
} from 'database'
|
||||
@ -348,6 +349,7 @@ export class ContributionResolver {
|
||||
): Promise<ContributionListResult> {
|
||||
// Check if only count was requested (without contributionList)
|
||||
const fields = Object.keys(extractGraphQLFields(info))
|
||||
// console.log(`fields: ${fields}`)
|
||||
const countOnly: boolean = fields.includes('contributionCount') && fields.length === 1
|
||||
// check if related user was requested
|
||||
const userRequested =
|
||||
@ -370,8 +372,25 @@ export class ContributionResolver {
|
||||
},
|
||||
countOnly,
|
||||
)
|
||||
const result = new ContributionListResult(count, dbContributions)
|
||||
|
||||
return new ContributionListResult(count, dbContributions)
|
||||
const uniqueUserIds = new Set<number>()
|
||||
const addIfExist = (userId?: number | null) => (userId ? uniqueUserIds.add(userId) : null)
|
||||
|
||||
for (const contribution of result.contributionList) {
|
||||
addIfExist(contribution.updatedBy)
|
||||
addIfExist(contribution.moderatorId)
|
||||
addIfExist(contribution.closedBy)
|
||||
}
|
||||
const users = await findUserNamesByIds(Array.from(uniqueUserIds))
|
||||
const getNameById = (userId?: number | null) => (userId ? (users.get(userId) ?? null) : null)
|
||||
|
||||
for (const contribution of result.contributionList) {
|
||||
contribution.updatedByUserName = getNameById(contribution.updatedBy)
|
||||
contribution.moderatorUserName = getNameById(contribution.moderatorId)
|
||||
contribution.closedByUserName = getNameById(contribution.closedBy)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION])
|
||||
|
||||
@ -15,6 +15,8 @@ import {
|
||||
EncryptedTransferArgs,
|
||||
fullName,
|
||||
interpretEncryptedTransferArgs,
|
||||
sendTransactionLinkRedeemedEmail,
|
||||
sendTransactionReceivedEmail,
|
||||
TransactionTypeId,
|
||||
} from 'core'
|
||||
import { randomBytes } from 'crypto'
|
||||
@ -29,6 +31,7 @@ import {
|
||||
User as DbUser,
|
||||
findModeratorCreatingContributionLink,
|
||||
findTransactionLinkByCode,
|
||||
findUserByIdentifier,
|
||||
getHomeCommunity,
|
||||
getLastTransaction,
|
||||
} from 'database'
|
||||
@ -567,7 +570,7 @@ export class TransactionLinkResolver {
|
||||
} catch (e) {
|
||||
const errmsg = `Error on creating Redeem JWT: error=${e}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -609,19 +612,19 @@ export class TransactionLinkResolver {
|
||||
if (!senderCom) {
|
||||
const errmsg = `Sender community not found with uuid=${senderCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const senderFedCom = await DbFederatedCommunity.findOneBy({ publicKey: senderCom.publicKey })
|
||||
if (!senderFedCom) {
|
||||
const errmsg = `Sender federated community not found with publicKey=${senderCom.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const recipientCom = await getCommunityByUuid(recipientCommunityUuid)
|
||||
if (!recipientCom) {
|
||||
const errmsg = `Recipient community not found with uuid=${recipientCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const client = DisbursementClientFactory.getInstance(senderFedCom)
|
||||
if (client instanceof V1_0_DisbursementClient) {
|
||||
@ -660,6 +663,64 @@ export class TransactionLinkResolver {
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug('Disburse JWT was sent successfully with result=', result)
|
||||
}
|
||||
/* don't send email here, because it is sent by the sender community
|
||||
const senderUser = await findUserByIdentifier(senderGradidoId, senderCommunityUuid)
|
||||
if (!senderUser) {
|
||||
const errmsg = `Sender user not found with identifier=${senderGradidoId}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const recipientUser = await findUserByIdentifier(recipientGradidoId, recipientCommunityUuid)
|
||||
if (!recipientUser) {
|
||||
const errmsg = `Recipient user not found with identifier=${recipientGradidoId}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if (recipientUser.emailContact?.email !== null) {
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'Sending TransactionLinkRedeem Email to recipient=' +
|
||||
recipientUser.firstName +
|
||||
' ' +
|
||||
recipientUser.lastName +
|
||||
'sender=' +
|
||||
senderUser.firstName +
|
||||
' ' +
|
||||
senderUser.lastName,
|
||||
)
|
||||
}
|
||||
try {
|
||||
await sendTransactionLinkRedeemedEmail({
|
||||
firstName: recipientUser.firstName,
|
||||
lastName: recipientUser.lastName,
|
||||
email: recipientUser.emailContact.email,
|
||||
language: recipientUser.language,
|
||||
senderFirstName: senderUser.firstName,
|
||||
senderLastName: senderUser.lastName,
|
||||
senderEmail: senderUser.emailContact?.email,
|
||||
transactionMemo: memo,
|
||||
transactionAmount: new Decimal(amount),
|
||||
})
|
||||
} catch (e) {
|
||||
const errmsg = `Send TransactionLinkRedeem Email to recipient failed with error=${e}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
} else {
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'Sender or Recipient are foreign users with no email contact, not sending Transaction Received Email: recipient=' +
|
||||
recipientUser.firstName +
|
||||
' ' +
|
||||
recipientUser.lastName +
|
||||
'sender=' +
|
||||
senderUser.firstName +
|
||||
' ' +
|
||||
senderUser.lastName,
|
||||
)
|
||||
}
|
||||
}
|
||||
*/
|
||||
} catch (e) {
|
||||
const errmsg = `Disburse JWT was not sent successfully with error=${e}`
|
||||
methodLogger.error(errmsg)
|
||||
|
||||
@ -214,6 +214,7 @@ export const executeTransaction = async (
|
||||
transactionAmount: amount,
|
||||
})
|
||||
if (transactionLink) {
|
||||
const recipientCom = await getCommunityName(recipient.communityUuid)
|
||||
await sendTransactionLinkRedeemedEmail({
|
||||
firstName: sender.firstName,
|
||||
lastName: sender.lastName,
|
||||
@ -221,7 +222,7 @@ export const executeTransaction = async (
|
||||
language: sender.language,
|
||||
senderFirstName: recipient.firstName,
|
||||
senderLastName: recipient.lastName,
|
||||
senderEmail: recipient.emailContact.email,
|
||||
senderEmail: recipientCom, // recipient.emailContact.email,
|
||||
transactionAmount: amount,
|
||||
transactionMemo: memo,
|
||||
})
|
||||
|
||||
@ -66,6 +66,10 @@ export const findContributions = async (
|
||||
if (relations?.user) {
|
||||
qb.orWhere('user.first_name LIKE :firstName', { firstName: queryString })
|
||||
.orWhere('user.last_name LIKE :lastName', { lastName: queryString })
|
||||
.orWhere('user.alias LIKE :alias', { alias: queryString })
|
||||
.orWhere("LOWER(CONCAT(user.first_name, ' ', user.last_name)) LIKE LOWER(:fullName)", {
|
||||
fullName: queryString.toLowerCase(),
|
||||
})
|
||||
.orWhere('emailContact.email LIKE :emailContact', { emailContact: queryString })
|
||||
.orWhere({ memo: Like(queryString) })
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "config-schema",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido Config for validate config",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "core",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -22,8 +22,8 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "biome check --error-on-warnings .",
|
||||
"lint:fix": "biome check --error-on-warnings . --write",
|
||||
"locales": "scripts/sort.sh src/locales",
|
||||
"locales:fix": "scripts/sort.sh src/locales --fix",
|
||||
"locales": "bun scripts/sortLocales.ts",
|
||||
"locales:fix": "bun scripts/sortLocales.ts --fix",
|
||||
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
51
core/scripts/sortLocales.ts
Normal file
51
core/scripts/sortLocales.ts
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bun
|
||||
import { readdir, readFile, writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
|
||||
const ROOT_DIR = join(import.meta.dir, '..')
|
||||
const LOCALES_DIR = join(ROOT_DIR, 'src', 'locales')
|
||||
|
||||
const FIX = process.argv.includes('--fix')
|
||||
|
||||
function sortObject(value: any): any {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(sortObject)
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.keys(value)
|
||||
.sort()
|
||||
.reduce<Record<string, any>>((acc, key) => {
|
||||
acc[key] = sortObject(value[key])
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
let exitCode = 0
|
||||
|
||||
const files = (await readdir(LOCALES_DIR))
|
||||
.filter(f => f.endsWith('.json'))
|
||||
|
||||
for (const file of files) {
|
||||
const path = join(LOCALES_DIR, file)
|
||||
|
||||
const originalText = await readFile(path, 'utf8')
|
||||
const originalJson = JSON.parse(originalText)
|
||||
|
||||
const sortedJson = sortObject(originalJson)
|
||||
const sortedText = JSON.stringify(sortedJson, null, 2) + '\n'
|
||||
|
||||
if (originalText !== sortedText) {
|
||||
if (FIX) {
|
||||
await writeFile(path, sortedText)
|
||||
} else {
|
||||
console.error(`${file} is not sorted by keys`)
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(exitCode)
|
||||
55
core/src/command/BaseCommand.ts
Normal file
55
core/src/command/BaseCommand.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
import { Command } from './Command'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.BaseCommand.${method}`)
|
||||
|
||||
export abstract class BaseCommand<T = any> implements Command<T> {
|
||||
protected abstract requiredFields: string[]
|
||||
|
||||
protected constructor(protected readonly params: any[]) {
|
||||
// this.validateRequiredFields();
|
||||
}
|
||||
|
||||
abstract execute(): Promise<string | boolean | null | Error>
|
||||
|
||||
private validateRequiredFields(): void {
|
||||
const methodLogger = createLogger(`validateRequiredFields`)
|
||||
if (!this.requiredFields || this.requiredFields.length === 0) {
|
||||
methodLogger.debug(`validateRequiredFields() no required fields`)
|
||||
return
|
||||
}
|
||||
methodLogger.debug(
|
||||
`validateRequiredFields() requiredFields=${JSON.stringify(this.requiredFields)}`,
|
||||
)
|
||||
/*
|
||||
const commandArgs = JSON.parse(this.params[0])
|
||||
const missingFields = this.requiredFields.filter(field =>
|
||||
commandArgs.{ field } === undefined || commandArgs.{ field } === null || commandArgs.{ field } === ''
|
||||
);
|
||||
methodLogger.debug(`validateRequiredFields() missingFields=${JSON.stringify(missingFields)}`)
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
methodLogger.error(`validateRequiredFields() missing fields: ${missingFields.join(', ')}`)
|
||||
throw new Error(`Missing required fields for ${this.constructor.name}: ${missingFields.join(', ')}`);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
validate(): boolean {
|
||||
const methodLogger = createLogger(`validate`)
|
||||
methodLogger.debug(
|
||||
`validate() requiredFields=${JSON.stringify(this.requiredFields)} params=${JSON.stringify(this.params)}`,
|
||||
)
|
||||
/*
|
||||
const isValid = this.requiredFields.every(field =>
|
||||
this.params[field] !== undefined &&
|
||||
this.params[field] !== null &&
|
||||
this.params[field] !== ''
|
||||
);
|
||||
methodLogger.debug(`validate() isValid=${isValid}`)
|
||||
*/
|
||||
return true
|
||||
}
|
||||
}
|
||||
4
core/src/command/Command.ts
Normal file
4
core/src/command/Command.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Command<_T = any> {
|
||||
execute(): Promise<string | boolean | null | Error>
|
||||
validate?(): boolean
|
||||
}
|
||||
77
core/src/command/CommandExecutor.ts
Normal file
77
core/src/command/CommandExecutor.ts
Normal file
@ -0,0 +1,77 @@
|
||||
// core/src/command/CommandExecutor.ts
|
||||
|
||||
import { getLogger } from 'log4js'
|
||||
import { CommandJwtPayloadType } from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
import { interpretEncryptedTransferArgs } from '../graphql/logic/interpretEncryptedTransferArgs'
|
||||
import { CommandResult } from '../graphql/model/CommandResult'
|
||||
import { EncryptedTransferArgs } from '../graphql/model/EncryptedTransferArgs'
|
||||
import { Command } from './Command'
|
||||
import { CommandFactory } from './CommandFactory'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandExecutor.${method}`)
|
||||
|
||||
export class CommandExecutor {
|
||||
async executeCommand<T>(command: Command<T>): Promise<CommandResult> {
|
||||
const methodLogger = createLogger(`executeCommand`)
|
||||
methodLogger.debug(`executeCommand() command=${command.constructor.name}`)
|
||||
try {
|
||||
if (command.validate && !command.validate()) {
|
||||
const errmsg = `Command validation failed for command=${command.constructor.name}`
|
||||
methodLogger.error(errmsg)
|
||||
return { success: false, error: errmsg }
|
||||
}
|
||||
methodLogger.debug(`executeCommand() executing command=${command.constructor.name}`)
|
||||
const result = await command.execute()
|
||||
methodLogger.debug(`executeCommand() executed result=${result}`)
|
||||
return { success: true, data: result }
|
||||
} catch (error) {
|
||||
methodLogger.error(`executeCommand() error=${error}`)
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async executeEncryptedCommand<_T>(encryptedArgs: EncryptedTransferArgs): Promise<CommandResult> {
|
||||
const methodLogger = createLogger(`executeEncryptedCommand`)
|
||||
try {
|
||||
// Decrypt the command data
|
||||
const commandArgs = (await interpretEncryptedTransferArgs(
|
||||
encryptedArgs,
|
||||
)) as CommandJwtPayloadType
|
||||
if (!commandArgs) {
|
||||
const errmsg = `invalid commandArgs payload of requesting community with publicKey=${encryptedArgs.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`executeEncryptedCommand() commandArgs=${JSON.stringify(commandArgs)}`)
|
||||
}
|
||||
const command = CommandFactory.getInstance().createCommand(
|
||||
commandArgs.commandName,
|
||||
commandArgs.commandArgs,
|
||||
)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`executeEncryptedCommand() command=${JSON.stringify(command)}`)
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
const result = await this.executeCommand(command)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`executeCommand() result=${JSON.stringify(result)}`)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
methodLogger.error(`executeEncryptedCommand() error=${error}`)
|
||||
const errorResult: CommandResult = {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to process command',
|
||||
}
|
||||
return errorResult
|
||||
}
|
||||
}
|
||||
}
|
||||
81
core/src/command/CommandFactory.ts
Normal file
81
core/src/command/CommandFactory.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
|
||||
import { BaseCommand } from './BaseCommand'
|
||||
import { Command } from './Command'
|
||||
import { ICommandConstructor } from './CommandTypes'
|
||||
// import { ICommandConstructor } from './CommandTypes';
|
||||
import { SendEmailCommand } from './commands/SendEmailCommand'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.CommandFactory.${method}`)
|
||||
|
||||
export class CommandFactory {
|
||||
private static instance: CommandFactory
|
||||
private commands: Map<string, ICommandConstructor> = new Map()
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): CommandFactory {
|
||||
if (!CommandFactory.instance) {
|
||||
CommandFactory.instance = new CommandFactory()
|
||||
}
|
||||
return CommandFactory.instance
|
||||
}
|
||||
|
||||
registerCommand<T>(name: string, commandClass: ICommandConstructor<T>): void {
|
||||
const methodLogger = createLogger(`registerCommand`)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`registerCommand() name=${name}, commandClass=${commandClass.name}`)
|
||||
}
|
||||
this.commands.set(name, commandClass)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`registerCommand() commands=${JSON.stringify(this.commands.entries())}`)
|
||||
}
|
||||
}
|
||||
|
||||
createCommand<T>(name: string, params: string[]): Command<T> {
|
||||
const methodLogger = createLogger(`createCommand`)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`createCommand() name=${name} params=${JSON.stringify(params)}`)
|
||||
}
|
||||
const CommandClass = this.commands.get(name)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
`createCommand() name=${name} commandClass=${CommandClass ? CommandClass.name : 'null'}`,
|
||||
)
|
||||
}
|
||||
if (CommandClass === undefined) {
|
||||
const errmsg = `Command ${name} not found`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
/*
|
||||
try {
|
||||
const command = new CommandClass(params) as Command<T>;
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`createCommand() command=${JSON.stringify(command)}`)
|
||||
}
|
||||
return command;
|
||||
} catch (error) {
|
||||
const errmsg = `Failed to create command ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
||||
methodLogger.error(errmsg);
|
||||
throw new Error(errmsg);
|
||||
}
|
||||
*/
|
||||
let command: BaseCommand
|
||||
switch (CommandClass.name) {
|
||||
case 'SendEmailCommand':
|
||||
command = new SendEmailCommand(params)
|
||||
break
|
||||
default: {
|
||||
const errmsg = `Command ${name} not found`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
}
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`createCommand() created command=${JSON.stringify(command)}`)
|
||||
}
|
||||
return command
|
||||
}
|
||||
}
|
||||
5
core/src/command/CommandTypes.ts
Normal file
5
core/src/command/CommandTypes.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Command } from './Command'
|
||||
|
||||
export interface ICommandConstructor<T = any> {
|
||||
new (params: any): Command<T>
|
||||
}
|
||||
145
core/src/command/commands/SendEmailCommand.ts
Normal file
145
core/src/command/commands/SendEmailCommand.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { findUserByUuids } from 'database'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { sendTransactionReceivedEmail } from '../../emails/sendEmailVariants'
|
||||
import { BaseCommand } from '../BaseCommand'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.command.commands.SendEmailCommand.${method}`)
|
||||
|
||||
export interface SendEmailCommandParams {
|
||||
mailType: string
|
||||
senderComUuid: string
|
||||
senderGradidoId: string
|
||||
receiverComUuid: string
|
||||
receiverGradidoId: string
|
||||
memo?: string
|
||||
amount?: string
|
||||
}
|
||||
export class SendEmailCommand extends BaseCommand<
|
||||
Record<string, unknown> | boolean | null | Error
|
||||
> {
|
||||
static readonly SEND_MAIL_COMMAND = 'SEND_MAIL_COMMAND'
|
||||
protected requiredFields: string[] = [
|
||||
'mailType',
|
||||
'senderComUuid',
|
||||
'senderGradidoId',
|
||||
'receiverComUuid',
|
||||
'receiverGradidoId',
|
||||
]
|
||||
protected sendEmailCommandParams: SendEmailCommandParams
|
||||
|
||||
constructor(params: any[]) {
|
||||
const methodLogger = createLogger(`constructor`)
|
||||
methodLogger.debug(`constructor() params=${JSON.stringify(params)}`)
|
||||
super(params)
|
||||
this.sendEmailCommandParams = JSON.parse(params[0]) as SendEmailCommandParams
|
||||
}
|
||||
|
||||
validate(): boolean {
|
||||
const baseValid = super.validate()
|
||||
if (!baseValid) {
|
||||
return false
|
||||
}
|
||||
// Additional validations
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async execute(): Promise<string | boolean | null | Error> {
|
||||
const methodLogger = createLogger(`execute`)
|
||||
methodLogger.debug(
|
||||
`execute() sendEmailCommandParams=${JSON.stringify(this.sendEmailCommandParams)}`,
|
||||
)
|
||||
let result: string
|
||||
if (!this.validate()) {
|
||||
throw new Error('Invalid command parameters')
|
||||
}
|
||||
// find sender user
|
||||
methodLogger.debug(
|
||||
`find sender user: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`,
|
||||
)
|
||||
const senderUser = await findUserByUuids(
|
||||
this.sendEmailCommandParams.senderComUuid,
|
||||
this.sendEmailCommandParams.senderGradidoId,
|
||||
true,
|
||||
)
|
||||
methodLogger.debug(`senderUser=${JSON.stringify(senderUser)}`)
|
||||
if (!senderUser) {
|
||||
const errmsg = `Sender user not found: ${this.sendEmailCommandParams.senderComUuid} ${this.sendEmailCommandParams.senderGradidoId}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
methodLogger.debug(
|
||||
`find recipient user: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`,
|
||||
)
|
||||
const recipientUser = await findUserByUuids(
|
||||
this.sendEmailCommandParams.receiverComUuid,
|
||||
this.sendEmailCommandParams.receiverGradidoId,
|
||||
)
|
||||
methodLogger.debug(`recipientUser=${JSON.stringify(recipientUser)}`)
|
||||
if (!recipientUser) {
|
||||
const errmsg = `Recipient user not found: ${this.sendEmailCommandParams.receiverComUuid} ${this.sendEmailCommandParams.receiverGradidoId}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
const emailParams = {
|
||||
firstName: recipientUser.firstName,
|
||||
lastName: recipientUser.lastName,
|
||||
email: recipientUser.emailContact.email,
|
||||
language: recipientUser.language,
|
||||
senderFirstName: senderUser.firstName,
|
||||
senderLastName: senderUser.lastName,
|
||||
senderEmail: senderUser.emailId !== null ? senderUser.emailContact.email : null,
|
||||
memo: this.sendEmailCommandParams.memo || '',
|
||||
transactionAmount: new Decimal(this.sendEmailCommandParams.amount || 0).abs(),
|
||||
}
|
||||
methodLogger.debug(`emailParams=${JSON.stringify(emailParams)}`)
|
||||
switch (this.sendEmailCommandParams.mailType) {
|
||||
case 'sendTransactionReceivedEmail': {
|
||||
const emailResult = await sendTransactionReceivedEmail(emailParams)
|
||||
result = this.getEmailResult(emailResult)
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown mail type: ${this.sendEmailCommandParams.mailType}`)
|
||||
}
|
||||
|
||||
try {
|
||||
// Example: const result = await emailService.sendEmail(this.params);
|
||||
return result
|
||||
} catch (error) {
|
||||
methodLogger.error('Error executing SendEmailCommand:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private getEmailResult(result: Record<string, unknown> | boolean | null | Error): string {
|
||||
const methodLogger = createLogger(`getEmailResult`)
|
||||
if (methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`result=${JSON.stringify(result)}`)
|
||||
}
|
||||
let emailResult: string
|
||||
if (result === null) {
|
||||
emailResult = `result is null`
|
||||
} else if (typeof result === 'boolean') {
|
||||
emailResult = `result is ${result}`
|
||||
} else if (result instanceof Error) {
|
||||
emailResult = `error-message is ${result.message}`
|
||||
} else if (typeof result === 'object') {
|
||||
// {"accepted":["stage5@gradido.net"],"rejected":[],"ehlo":["PIPELINING","SIZE 25600000","ETRN","AUTH DIGEST-MD5 CRAM-MD5 PLAIN LOGIN","ENHANCEDSTATUSCODES","8BITMIME","DSN","CHUNKING"],"envelopeTime":23,"messageTime":135,"messageSize":37478,"response":"250 2.0.0 Ok: queued as C45C2100BD7","envelope":{"from":"stage5@gradido.net","to":["stage5@gradido.net"]},"messageId":"<d269161f-f3d2-2c96-49c0-58154366271b@gradido.net>"
|
||||
const accepted = (result as Record<string, unknown>).accepted
|
||||
const messageSize = (result as Record<string, unknown>).messageSize
|
||||
const response = (result as Record<string, unknown>).response
|
||||
const envelope = JSON.stringify((result as Record<string, unknown>).envelope)
|
||||
emailResult = `accepted=${accepted}, messageSize=${messageSize}, response=${response}, envelope=${envelope}`
|
||||
} else {
|
||||
emailResult = `result is unknown type`
|
||||
}
|
||||
|
||||
return emailResult
|
||||
}
|
||||
}
|
||||
11
core/src/command/initCommands.ts
Normal file
11
core/src/command/initCommands.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { CommandFactory } from './CommandFactory'
|
||||
import { SendEmailCommand } from './commands/SendEmailCommand'
|
||||
// Import other commands...
|
||||
|
||||
export function initializeCommands(): void {
|
||||
const factory = CommandFactory.getInstance()
|
||||
|
||||
// Register all commands
|
||||
factory.registerCommand(SendEmailCommand.SEND_MAIL_COMMAND, SendEmailCommand)
|
||||
// Register other commands...
|
||||
}
|
||||
@ -77,34 +77,46 @@ export const sendEmailTranslated = async ({
|
||||
...receiver,
|
||||
attachments: [
|
||||
{
|
||||
// filename: 'gradido-header.jpeg',
|
||||
filename: 'gradido-header.jpeg',
|
||||
content: gradidoHeader,
|
||||
cid: 'gradidoheader',
|
||||
contentType: 'image/jpeg',
|
||||
contentDisposition: 'inline',
|
||||
},
|
||||
{
|
||||
// filename: 'facebook-icon.png',
|
||||
filename: 'facebook-icon.png',
|
||||
content: facebookIcon,
|
||||
cid: 'facebookicon',
|
||||
contentType: 'image/png',
|
||||
contentDisposition: 'inline',
|
||||
},
|
||||
{
|
||||
// filename: 'telegram-icon.png',
|
||||
filename: 'telegram-icon.png',
|
||||
content: telegramIcon,
|
||||
cid: 'telegramicon',
|
||||
contentType: 'image/png',
|
||||
contentDisposition: 'inline',
|
||||
},
|
||||
{
|
||||
// filename: 'twitter-icon.png',
|
||||
filename: 'twitter-icon.png',
|
||||
content: twitterIcon,
|
||||
cid: 'twittericon',
|
||||
contentType: 'image/png',
|
||||
contentDisposition: 'inline',
|
||||
},
|
||||
{
|
||||
// filename: 'youtube-icon.png',
|
||||
filename: 'youtube-icon.png',
|
||||
content: youtubeIcon,
|
||||
cid: 'youtubeicon',
|
||||
contentType: 'image/png',
|
||||
contentDisposition: 'inline',
|
||||
},
|
||||
{
|
||||
// filename: 'chatbox-icon.png',
|
||||
filename: 'chatbox-icon.png',
|
||||
content: chatboxIcon,
|
||||
cid: 'chatboxicon',
|
||||
contentType: 'image/png',
|
||||
contentDisposition: 'inline',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -25,6 +25,7 @@ CONFIG.EMAIL_SENDER = 'info@gradido.net'
|
||||
CONFIG.EMAIL_SMTP_HOST = testMailServerHost
|
||||
CONFIG.EMAIL_SMTP_PORT = testMailServerPort
|
||||
CONFIG.EMAIL_TLS = testMailTLS
|
||||
CONFIG.EMAIL_TEST_MODUS = false
|
||||
|
||||
mock.module('nodemailer', () => {
|
||||
return {
|
||||
|
||||
@ -175,18 +175,18 @@ export const sendTransactionReceivedEmail = (
|
||||
data: EmailCommonData & {
|
||||
senderFirstName: string
|
||||
senderLastName: string
|
||||
senderEmail: string
|
||||
senderEmail: string | null
|
||||
memo: string
|
||||
transactionAmount: Decimal
|
||||
},
|
||||
): Promise<Record<string, unknown> | boolean | null | Error> => {
|
||||
return sendEmailTranslated({
|
||||
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
|
||||
template: 'transactionReceived',
|
||||
template: data.senderEmail !== null ? 'transactionReceived' : 'transactionReceivedNoSender',
|
||||
locals: {
|
||||
...data,
|
||||
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
|
||||
...getEmailCommonLocales(),
|
||||
...(data.senderEmail !== null ? getEmailCommonLocales() : { locale: data.language }),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
extend ../layout.pug
|
||||
|
||||
block content
|
||||
//
|
||||
mixin mailto(email, subject)
|
||||
- var formattedSubject = encodeURIComponent(subject)
|
||||
a(class!=attributes.class href=`mailto:${email}?subject=${formattedSubject}`)
|
||||
block
|
||||
|
||||
- var subject= t('emails.transactionReceived.replySubject', { senderFirstName, senderLastName, transactionAmount })
|
||||
h2= t('emails.transactionReceived.title', { senderFirstName, senderLastName, transactionAmount })
|
||||
.text-block
|
||||
include ../includes/salutation.pug
|
||||
p
|
||||
= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName })
|
||||
.content
|
||||
h2= t('emails.general.message')
|
||||
.child-left
|
||||
div(class="p_content")= memo
|
||||
|
||||
a.button-3(href=`${communityURL}/transactions`) #{t('emails.general.toAccount')}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||
45
core/src/federation/client/1_0/CommandClient.ts
Normal file
45
core/src/federation/client/1_0/CommandClient.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { FederatedCommunity as DbFederatedCommunity } from 'database'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../../config/const'
|
||||
import { EncryptedTransferArgs } from '../../../graphql/model/EncryptedTransferArgs'
|
||||
import { ensureUrlEndsWithSlash } from '../../../util/utilities'
|
||||
import { sendCommand as sendCommandQuery } from './query/sendCommand'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.CommandClient`)
|
||||
|
||||
export class CommandClient {
|
||||
dbCom: DbFederatedCommunity
|
||||
endpoint: string
|
||||
client: GraphQLClient
|
||||
|
||||
constructor(dbCom: DbFederatedCommunity) {
|
||||
this.dbCom = dbCom
|
||||
this.endpoint = ensureUrlEndsWithSlash(dbCom.endPoint).concat(dbCom.apiVersion).concat('/')
|
||||
this.client = new GraphQLClient(this.endpoint, {
|
||||
method: 'POST',
|
||||
jsonSerializer: {
|
||||
parse: JSON.parse,
|
||||
stringify: JSON.stringify,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async sendCommand(args: EncryptedTransferArgs): Promise<boolean> {
|
||||
logger.debug(`sendCommand at ${this.endpoint} for args:`, args)
|
||||
try {
|
||||
const { data } = await this.client.rawRequest<{ success: boolean }>(sendCommandQuery, {
|
||||
args,
|
||||
})
|
||||
if (!data?.success) {
|
||||
logger.warn('sendCommand without response data from endpoint', this.endpoint)
|
||||
return false
|
||||
}
|
||||
logger.debug('sendCommand successfully started with endpoint', this.endpoint)
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error('error on sendCommand: ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
11
core/src/federation/client/1_0/query/sendCommand.ts
Normal file
11
core/src/federation/client/1_0/query/sendCommand.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const sendCommand = gql`
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
sendCommand(encryptedArgs: $args) {
|
||||
success
|
||||
data
|
||||
error
|
||||
}
|
||||
}
|
||||
`
|
||||
3
core/src/federation/client/1_1/CommandClient.ts
Normal file
3
core/src/federation/client/1_1/CommandClient.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { CommandClient as V1_0_CommandClient } from '../1_0/CommandClient'
|
||||
|
||||
export class CommandClient extends V1_0_CommandClient {}
|
||||
55
core/src/federation/client/CommandClientFactory.ts
Normal file
55
core/src/federation/client/CommandClientFactory.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { ApiVersionType } from 'core'
|
||||
import { FederatedCommunity as DbFederatedCommunity } from 'database'
|
||||
import { CommandClient as V1_0_CommandClient } from './1_0/CommandClient'
|
||||
import { CommandClient as V1_1_CommandClient } from './1_1/CommandClient'
|
||||
|
||||
type CommandClient = V1_0_CommandClient | V1_1_CommandClient
|
||||
|
||||
interface CommandClientInstance {
|
||||
id: number
|
||||
|
||||
client: CommandClient
|
||||
}
|
||||
|
||||
export class CommandClientFactory {
|
||||
private static instanceArray: CommandClientInstance[] = []
|
||||
|
||||
/**
|
||||
* The Singleton's constructor should always be private to prevent direct
|
||||
* construction calls with the `new` operator.
|
||||
*/
|
||||
|
||||
private constructor() {}
|
||||
|
||||
private static createCommandClient = (dbCom: DbFederatedCommunity) => {
|
||||
switch (dbCom.apiVersion) {
|
||||
case ApiVersionType.V1_0:
|
||||
return new V1_0_CommandClient(dbCom)
|
||||
case ApiVersionType.V1_1:
|
||||
return new V1_1_CommandClient(dbCom)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The static method that controls the access to the singleton instance.
|
||||
*
|
||||
* This implementation let you subclass the Singleton class while keeping
|
||||
* just one instance of each subclass around.
|
||||
*/
|
||||
public static getInstance(dbCom: DbFederatedCommunity): CommandClient | null {
|
||||
const instance = CommandClientFactory.instanceArray.find((instance) => instance.id === dbCom.id)
|
||||
if (instance) {
|
||||
return instance.client
|
||||
}
|
||||
const client = CommandClientFactory.createCommandClient(dbCom)
|
||||
if (client) {
|
||||
CommandClientFactory.instanceArray.push({
|
||||
id: dbCom.id,
|
||||
client,
|
||||
} as CommandClientInstance)
|
||||
}
|
||||
return client
|
||||
}
|
||||
}
|
||||
14
core/src/graphql/logic/processCommand.ts
Normal file
14
core/src/graphql/logic/processCommand.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
|
||||
const createLogger = (method: string) =>
|
||||
getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.logic.processCommand.${method}`)
|
||||
|
||||
export async function processCommand(
|
||||
commandClass: string,
|
||||
commandMethod: string,
|
||||
commandArgs: string[],
|
||||
) {
|
||||
const methodLogger = createLogger(`processCommand`)
|
||||
methodLogger.info('processing a command...')
|
||||
}
|
||||
@ -16,6 +16,7 @@ import { Decimal } from 'decimal.js-light'
|
||||
// import { LogError } from '@server/LogError'
|
||||
import { getLogger } from 'log4js'
|
||||
import {
|
||||
CommandJwtPayloadType,
|
||||
encryptAndSign,
|
||||
PendingTransactionState,
|
||||
SendCoinsJwtPayloadType,
|
||||
@ -23,11 +24,15 @@ import {
|
||||
verifyAndDecrypt,
|
||||
} from 'shared'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
import { SendEmailCommand } from '../../command/commands/SendEmailCommand'
|
||||
import { CONFIG as CONFIG_CORE } from '../../config'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail } from '../../emails'
|
||||
import { CommandClient as V1_0_CommandClient } from '../../federation/client/1_0/CommandClient'
|
||||
import { SendCoinsResultLoggingView } from '../../federation/client/1_0/logging/SendCoinsResultLogging.view'
|
||||
import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult'
|
||||
import { SendCoinsClient as V1_0_SendCoinsClient } from '../../federation/client/1_0/SendCoinsClient'
|
||||
import { CommandClientFactory } from '../../federation/client/CommandClientFactory'
|
||||
import { SendCoinsClientFactory } from '../../federation/client/SendCoinsClientFactory'
|
||||
import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
|
||||
import { EncryptedTransferArgs } from '../../graphql/model/EncryptedTransferArgs'
|
||||
@ -167,6 +172,19 @@ export async function processXComCompleteTransaction(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (dbTransactionLink) {
|
||||
await sendTransactionLinkRedeemedEmail({
|
||||
firstName: senderUser.firstName,
|
||||
lastName: senderUser.lastName,
|
||||
email: senderUser.emailContact.email,
|
||||
language: senderUser.language,
|
||||
senderFirstName: foreignUser.firstName,
|
||||
senderLastName: foreignUser.lastName,
|
||||
senderEmail: recipientCom.name!, // foreignUser.emailContact.email,
|
||||
transactionAmount: new Decimal(amount),
|
||||
transactionMemo: memo,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const errmsg =
|
||||
@ -227,7 +245,7 @@ export async function processXComPendingSendCoins(
|
||||
|
||||
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
|
||||
where: {
|
||||
publicKey: Buffer.from(receiverCom.publicKey),
|
||||
publicKey: receiverCom.publicKey,
|
||||
apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API,
|
||||
},
|
||||
})
|
||||
@ -484,6 +502,37 @@ export async function processXComCommittingSendCoins(
|
||||
sendCoinsResult.recipGradidoID = pendingTx.linkedUserGradidoID
|
||||
sendCoinsResult.recipAlias = recipient.recipAlias
|
||||
}
|
||||
// ** after successfull settle of the pending transaction on sender side we have to send a trigger to the recipient community to send an email to the x-com-tx recipient
|
||||
const cmdClient = CommandClientFactory.getInstance(receiverFCom)
|
||||
|
||||
if (cmdClient instanceof V1_0_CommandClient) {
|
||||
const payload = new CommandJwtPayloadType(
|
||||
handshakeID,
|
||||
SendEmailCommand.SEND_MAIL_COMMAND,
|
||||
SendEmailCommand.name,
|
||||
[
|
||||
JSON.stringify({
|
||||
mailType: 'sendTransactionReceivedEmail',
|
||||
senderComUuid: senderCom.communityUuid,
|
||||
senderGradidoId: sender.gradidoID,
|
||||
receiverComUuid: receiverCom.communityUuid,
|
||||
receiverGradidoId: sendCoinsResult.recipGradidoID,
|
||||
memo: pendingTx.memo,
|
||||
amount: pendingTx.amount,
|
||||
}),
|
||||
],
|
||||
)
|
||||
const jws = await encryptAndSign(
|
||||
payload,
|
||||
senderCom.privateJwtKey!,
|
||||
receiverCom.publicJwtKey!,
|
||||
)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
cmdClient.sendCommand(args)
|
||||
}
|
||||
} catch (err) {
|
||||
methodLogger.error(
|
||||
`Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`,
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
import { Community as DbCommunity, User as DbUser, findForeignUserByUuids } from 'database'
|
||||
import {
|
||||
Community as DbCommunity,
|
||||
User as DbUser,
|
||||
UserContact as DbUserContact,
|
||||
findForeignUserByUuids,
|
||||
UserContactLoggingView,
|
||||
UserLoggingView,
|
||||
} from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
import { UserContactType } from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { SendCoinsResult } from '../../federation/client/1_0/model/SendCoinsResult'
|
||||
|
||||
@ -35,17 +43,38 @@ export async function storeForeignUser(
|
||||
}
|
||||
foreignUser.gradidoID = committingResult.recipGradidoID
|
||||
foreignUser = await DbUser.save(foreignUser)
|
||||
logger.debug('new foreignUser inserted:', foreignUser)
|
||||
|
||||
logger.debug('new foreignUser inserted:', new UserLoggingView(foreignUser))
|
||||
/*
|
||||
if (committingResult.recipEmail !== null) {
|
||||
let foreignUserEmail = DbUserContact.create()
|
||||
foreignUserEmail.email = committingResult.recipEmail!
|
||||
foreignUserEmail.emailChecked = true
|
||||
foreignUserEmail.user = foreignUser
|
||||
foreignUserEmail = await DbUserContact.save(foreignUserEmail)
|
||||
logger.debug(
|
||||
'new foreignUserEmail inserted:',
|
||||
new UserContactLoggingView(foreignUserEmail),
|
||||
)
|
||||
foreignUser.emailContact = foreignUserEmail
|
||||
foreignUser.emailId = foreignUserEmail.id
|
||||
foreignUser = await DbUser.save(foreignUser)
|
||||
}
|
||||
*/
|
||||
return foreignUser
|
||||
} else if (
|
||||
user.firstName !== committingResult.recipFirstName ||
|
||||
user.lastName !== committingResult.recipLastName ||
|
||||
user.alias !== committingResult.recipAlias
|
||||
user.alias !== committingResult.recipAlias /* ||
|
||||
(user.emailContact === null && committingResult.recipEmail !== null) ||
|
||||
(user.emailContact !== null &&
|
||||
user.emailContact?.email !== null &&
|
||||
user.emailContact?.email !== committingResult.recipEmail)
|
||||
*/
|
||||
) {
|
||||
logger.warn(
|
||||
logger.debug(
|
||||
'foreignUser still exists, but with different name or alias:',
|
||||
user,
|
||||
new UserLoggingView(user),
|
||||
committingResult,
|
||||
)
|
||||
if (committingResult.recipFirstName !== null) {
|
||||
@ -57,11 +86,39 @@ export async function storeForeignUser(
|
||||
if (committingResult.recipAlias !== null) {
|
||||
user.alias = committingResult.recipAlias
|
||||
}
|
||||
/*
|
||||
if (!user.emailContact && committingResult.recipEmail !== null) {
|
||||
logger.debug(
|
||||
'creating new userContact:',
|
||||
new UserContactLoggingView(user.emailContact),
|
||||
committingResult,
|
||||
)
|
||||
let foreignUserEmail = DbUserContact.create()
|
||||
foreignUserEmail.type = UserContactType.USER_CONTACT_EMAIL
|
||||
foreignUserEmail.email = committingResult.recipEmail!
|
||||
foreignUserEmail.emailChecked = true
|
||||
foreignUserEmail.user = user
|
||||
foreignUserEmail.userId = user.id
|
||||
foreignUserEmail = await DbUserContact.save(foreignUserEmail)
|
||||
logger.debug(
|
||||
'new foreignUserEmail inserted:',
|
||||
new UserContactLoggingView(foreignUserEmail),
|
||||
)
|
||||
user.emailContact = foreignUserEmail
|
||||
user.emailId = foreignUserEmail.id
|
||||
} else if (user.emailContact && committingResult.recipEmail != null) {
|
||||
const userContact = user.emailContact
|
||||
userContact.email = committingResult.recipEmail
|
||||
user.emailContact = await DbUserContact.save(userContact)
|
||||
user.emailId = userContact.id
|
||||
logger.debug('foreignUserEmail updated:', new UserContactLoggingView(userContact))
|
||||
}
|
||||
*/
|
||||
await DbUser.save(user)
|
||||
logger.debug('update recipient successful.', user)
|
||||
logger.debug('update recipient successful.', new UserLoggingView(user))
|
||||
return user
|
||||
} else {
|
||||
logger.debug('foreignUser still exists...:', user)
|
||||
logger.debug('foreignUser still exists...:', new UserLoggingView(user))
|
||||
return user
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
13
core/src/graphql/model/CommandResult.ts
Normal file
13
core/src/graphql/model/CommandResult.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Field, ObjectType } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class CommandResult {
|
||||
@Field(() => Boolean)
|
||||
success: boolean
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
data?: any
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
error?: string
|
||||
}
|
||||
@ -1,5 +1,9 @@
|
||||
export * from './command/CommandExecutor'
|
||||
export * from './command/CommandFactory'
|
||||
export * from './command/initCommands'
|
||||
export * from './config/index'
|
||||
export * from './emails'
|
||||
export { CommandClient as V1_0_CommandClient } from './federation/client/1_0/CommandClient'
|
||||
export * from './federation/client/1_0/logging/SendCoinsArgsLogging.view'
|
||||
export * from './federation/client/1_0/logging/SendCoinsResultLogging.view'
|
||||
export * from './federation/client/1_0/model/SendCoinsArgs'
|
||||
@ -9,15 +13,19 @@ export * from './federation/client/1_0/query/revertSettledSendCoins'
|
||||
export * from './federation/client/1_0/query/settleSendCoins'
|
||||
export * from './federation/client/1_0/query/voteForSendCoins'
|
||||
export { SendCoinsClient as V1_0_SendCoinsClient } from './federation/client/1_0/SendCoinsClient'
|
||||
export { CommandClient as V1_1_CommandClient } from './federation/client/1_1/CommandClient'
|
||||
export { SendCoinsClient as V1_1_SendCoinsClient } from './federation/client/1_1/SendCoinsClient'
|
||||
export * from './federation/client/CommandClientFactory'
|
||||
export * from './federation/client/SendCoinsClientFactory'
|
||||
export * from './federation/enum/apiVersionType'
|
||||
export * from './graphql/enum/TransactionTypeId'
|
||||
export * from './graphql/logging/DecayLogging.view'
|
||||
export * from './graphql/logic/interpretEncryptedTransferArgs'
|
||||
export * from './graphql/logic/processCommand'
|
||||
export * from './graphql/logic/processXComSendCoins'
|
||||
export * from './graphql/logic/settlePendingSenderTransaction'
|
||||
export * from './graphql/logic/storeForeignUser'
|
||||
export * from './graphql/model/CommandResult'
|
||||
export * from './graphql/model/Decay'
|
||||
export * from './graphql/model/EncryptedTransferArgs'
|
||||
export * from './logic'
|
||||
|
||||
@ -18,10 +18,10 @@
|
||||
},
|
||||
"addedContributionMessage": {
|
||||
"commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.",
|
||||
"message": "„{message}“",
|
||||
"readMessage": "Nachricht lesen und beantworten",
|
||||
"subject": "Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||
"title": "Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||
"message": "„{message}“",
|
||||
"toSeeAndAnswerMessage": "Um auf die Nachricht zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“."
|
||||
},
|
||||
"contribution": {
|
||||
@ -90,8 +90,8 @@
|
||||
},
|
||||
"transactionReceived": {
|
||||
"haveReceivedAmountGDDFrom": "du hast soeben {transactionAmount} GDD erhalten von {senderFirstName} {senderLastName}",
|
||||
"subject": "{senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet",
|
||||
"replySubject": "Re: {senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet",
|
||||
"subject": "{senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet",
|
||||
"title": "{senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet"
|
||||
}
|
||||
},
|
||||
|
||||
@ -18,10 +18,10 @@
|
||||
},
|
||||
"addedContributionMessage": {
|
||||
"commonGoodContributionMessage": "You have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
||||
"message": "„{message}“",
|
||||
"readMessage": "Read and reply to message",
|
||||
"subject": "Message about your common good contribution",
|
||||
"title": "Message about your common good contribution",
|
||||
"message": "„{message}“",
|
||||
"toSeeAndAnswerMessage": "To reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab."
|
||||
},
|
||||
"contribution": {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
CONFIG_VERSION=$DATABASE_CONFIG_VERSION
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=$DB_USER
|
||||
|
||||
@ -25,7 +25,7 @@ const run = async (command: string) => {
|
||||
host: ${CONFIG.DB_HOST}
|
||||
port: ${CONFIG.DB_PORT}
|
||||
user: ${CONFIG.DB_USER}
|
||||
password: ${CONFIG.DB_PASSWORD.slice(-2)}
|
||||
password: last 2 character: ${CONFIG.DB_PASSWORD.slice(-2)}
|
||||
database: ${CONFIG.DB_DATABASE}`,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "database",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { drizzle, MySql2Database } from 'drizzle-orm/mysql2'
|
||||
import Redis from 'ioredis'
|
||||
import { getLogger } from 'log4js'
|
||||
import { Connection, createConnection } from 'mysql2/promise'
|
||||
import { Connection, createConnection, createPool, Pool } from 'mysql2/promise'
|
||||
import { DataSource as DBDataSource, FileLogger } from 'typeorm'
|
||||
import { latestDbVersion } from '.'
|
||||
import { CONFIG } from './config'
|
||||
@ -14,7 +14,7 @@ export class AppDatabase {
|
||||
private static instance: AppDatabase
|
||||
private dataSource: DBDataSource | undefined
|
||||
private drizzleDataSource: MySql2Database | undefined
|
||||
private drizzleConnection: Connection | undefined
|
||||
private drizzlePool: Pool | undefined
|
||||
private redisClient: Redis | undefined
|
||||
|
||||
/**
|
||||
@ -48,10 +48,10 @@ export class AppDatabase {
|
||||
}
|
||||
|
||||
public getDrizzleDataSource(): MySql2Database {
|
||||
if (!this.drizzleDataSource) {
|
||||
throw new Error('Drizzle connection not initialized')
|
||||
if (!this.drizzlePool) {
|
||||
throw new Error('Drizzle connection pool not initialized')
|
||||
}
|
||||
return this.drizzleDataSource
|
||||
return drizzle({ client: this.drizzlePool })
|
||||
}
|
||||
|
||||
// create database connection, initialize with automatic retry and check for correct database version
|
||||
@ -106,22 +106,25 @@ export class AppDatabase {
|
||||
logger.info('Redis status=', this.redisClient.status)
|
||||
|
||||
if (!this.drizzleDataSource) {
|
||||
this.drizzleConnection = await createConnection({
|
||||
this.drizzlePool = createPool({
|
||||
host: CONFIG.DB_HOST,
|
||||
user: CONFIG.DB_USER,
|
||||
password: CONFIG.DB_PASSWORD,
|
||||
database: CONFIG.DB_DATABASE,
|
||||
port: CONFIG.DB_PORT,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 20,
|
||||
queueLimit: 100,
|
||||
enableKeepAlive: true,
|
||||
keepAliveInitialDelay: 10000,
|
||||
})
|
||||
this.drizzleDataSource = drizzle({ client: this.drizzleConnection })
|
||||
}
|
||||
}
|
||||
|
||||
public async destroy(): Promise<void> {
|
||||
await Promise.all([this.dataSource?.destroy(), this.drizzleConnection?.end()])
|
||||
await Promise.all([this.dataSource?.destroy(), this.drizzlePool?.end()])
|
||||
this.dataSource = undefined
|
||||
this.drizzleConnection = undefined
|
||||
this.drizzleDataSource = undefined
|
||||
this.drizzlePool = undefined
|
||||
if (this.redisClient) {
|
||||
await this.redisClient.quit()
|
||||
this.redisClient = undefined
|
||||
|
||||
@ -17,21 +17,25 @@ export class UserContactLoggingView extends AbstractLoggingView {
|
||||
|
||||
public toJSON(): any {
|
||||
return {
|
||||
id: this.self.id,
|
||||
type: this.self.type,
|
||||
user:
|
||||
this.showUser && this.self.user
|
||||
? new UserLoggingView(this.self.user).toJSON()
|
||||
: { id: this.self.userId },
|
||||
email: this.self.email?.substring(0, 3) + '...',
|
||||
emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...',
|
||||
emailOptInTypeId: OptInType[this.self.emailOptInTypeId],
|
||||
emailResendCount: this.self.emailResendCount,
|
||||
emailChecked: this.self.emailChecked,
|
||||
phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined,
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
deletedAt: this.dateToString(this.self.deletedAt),
|
||||
self: this.self
|
||||
? {
|
||||
id: this.self.id,
|
||||
type: this.self.type,
|
||||
user:
|
||||
this.showUser && this.self.user
|
||||
? new UserLoggingView(this.self.user).toJSON()
|
||||
: { id: this.self.userId },
|
||||
email: this.self.email?.substring(0, 3) + '...',
|
||||
emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...',
|
||||
emailOptInTypeId: OptInType[this.self.emailOptInTypeId],
|
||||
emailResendCount: this.self.emailResendCount,
|
||||
emailChecked: this.self.emailChecked,
|
||||
phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined,
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
deletedAt: this.dateToString(this.self.deletedAt),
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ afterAll(async () => {
|
||||
|
||||
describe('openaiThreads query test', () => {
|
||||
it('should insert a new openai thread', async () => {
|
||||
await Promise.resolve([dbInsertOpenaiThread('7', 1), dbInsertOpenaiThread('72', 6)])
|
||||
await Promise.all([dbInsertOpenaiThread('7', 1), dbInsertOpenaiThread('72', 6)])
|
||||
const result = await db.select().from(openaiThreadsTable)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result).toMatchObject([
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { aliasSchema, emailSchema, uuidv4Schema } from 'shared'
|
||||
import { Raw } from 'typeorm'
|
||||
import { In, Raw } from 'typeorm'
|
||||
import { User as DbUser, UserContact as DbUserContact } from '../entity'
|
||||
import { findWithCommunityIdentifier, LOG4JS_QUERIES_CATEGORY_NAME } from './index'
|
||||
|
||||
@ -81,3 +81,26 @@ export async function findForeignUserByUuids(
|
||||
where: { foreign: true, communityUuid, gradidoID },
|
||||
})
|
||||
}
|
||||
|
||||
export async function findUserByUuids(
|
||||
communityUuid: string,
|
||||
gradidoID: string,
|
||||
foreign: boolean = false,
|
||||
): Promise<DbUser | null> {
|
||||
return DbUser.findOne({
|
||||
where: { foreign, communityUuid, gradidoID },
|
||||
relations: ['emailContact'],
|
||||
})
|
||||
}
|
||||
|
||||
export async function findUserNamesByIds(userIds: number[]): Promise<Map<number, string>> {
|
||||
const users = await DbUser.find({
|
||||
select: { id: true, firstName: true, lastName: true, alias: true },
|
||||
where: { id: In(userIds) },
|
||||
})
|
||||
return new Map(
|
||||
users.map((user) => {
|
||||
return [user.id, `${user.firstName} ${user.lastName}`]
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
# Need to adjust!
|
||||
# Will be seen on login and from other communities if dht and federation modules are active
|
||||
COMMUNITY_NAME="Your community name"
|
||||
# Description should have at least 10 characters
|
||||
COMMUNITY_DESCRIPTION="Short Description from your Community."
|
||||
# your domain name, without protocol (without https:// or http:// )
|
||||
# domain name should be configured in your dns records to point to this server
|
||||
# hetzner_cloud/install.sh will be acquire a SSL-certificate via letsencrypt for this domain
|
||||
COMMUNITY_HOST=gddhost.tld
|
||||
# used in E-Mails and some error message, should be the email address of your own support (team)
|
||||
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
||||
|
||||
# setup email account for sending gradido system messages to users
|
||||
@ -12,10 +18,44 @@ EMAIL_PASSWORD=1234
|
||||
EMAIL_SMTP_HOST=smtp.lustig.de
|
||||
EMAIL_SMTP_PORT=587
|
||||
|
||||
BACKEND_PORT=4000
|
||||
|
||||
# Federation, Settings for transactions with other communities
|
||||
# if set to true allow sending gradidos to another communities
|
||||
FEDERATION_XCOM_SENDCOINS_ENABLED=false
|
||||
# if set to true, allow redeeming gradido link from another community
|
||||
CROSS_TX_REDEEM_LINK_ACTIVE=false
|
||||
|
||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||
# on an hash created from this topic
|
||||
# need for discover other communities
|
||||
# get the correct topic from Gradido Academy, GRADIDO_HUB is our test topic
|
||||
FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||
|
||||
# Advanced Server Setup
|
||||
|
||||
# SCSS Parsing
|
||||
# grass speed up frontend building, but needs some time for the first setup because it is a rust program which need to be compiled
|
||||
USE_GRASS=false
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
|
||||
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
|
||||
|
||||
# Need adjustments for test system
|
||||
# protocol for community host, on production usually https, on local dev usually http
|
||||
URL_PROTOCOL=https
|
||||
|
||||
# only for test server
|
||||
DEPLOY_SEED_DATA=false
|
||||
# test email
|
||||
# if true all email will be send to EMAIL_TEST_RECEIVER instead of email address of user
|
||||
EMAIL_TEST_MODUS=false
|
||||
EMAIL_TEST_RECEIVER=test_team@gradido.net
|
||||
|
||||
# webhook for auto update on github repository changes
|
||||
WEBHOOK_GITHUB_BRANCH=master
|
||||
|
||||
# Business Logic
|
||||
|
||||
# how many minutes email verification code is valid
|
||||
# also used for password reset code
|
||||
@ -23,36 +63,8 @@ EMAIL_CODE_VALID_TIME=1440
|
||||
# how many minutes user must wait before he can request the email verification code again
|
||||
# also used for password reset code
|
||||
EMAIL_CODE_REQUEST_TIME=10
|
||||
|
||||
# Need to adjust by updates
|
||||
# config versions
|
||||
DATABASE_CONFIG_VERSION=v1.2022-03-18
|
||||
BACKEND_CONFIG_VERSION=v23.2024-04-04
|
||||
FRONTEND_CONFIG_VERSION=v6.2024-02-27
|
||||
ADMIN_CONFIG_VERSION=v2.2024-01-04
|
||||
FEDERATION_CONFIG_VERSION=v2.2023-08-24
|
||||
FEDERATION_DHT_CONFIG_VERSION=v4.2024-01-17
|
||||
FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||
|
||||
# Need adjustments for test system
|
||||
URL_PROTOCOL=https
|
||||
# start script
|
||||
# only for test server
|
||||
DEPLOY_SEED_DATA=false
|
||||
# test email
|
||||
# if true all email will be send to EMAIL_TEST_RECEIVER instead of email address of user
|
||||
EMAIL_TEST_MODUS=false
|
||||
EMAIL_TEST_RECEIVER=test_team@gradido.net
|
||||
USE_CRYPTO_WORKER=true
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
|
||||
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
|
||||
|
||||
# webhook
|
||||
WEBHOOK_GITHUB_SECRET=secret
|
||||
WEBHOOK_GITHUB_BRANCH=master
|
||||
# login expire time
|
||||
JWT_EXPIRES_IN=10m
|
||||
|
||||
# frontend and admin paths, usually don't need changes
|
||||
# used in nginx config and for links in emails
|
||||
@ -66,44 +78,19 @@ EMAIL_LINK_SETPASSWORD_PATH=/reset-password/
|
||||
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
|
||||
EMAIL_LINK_OVERVIEW_PATH=/overview
|
||||
ADMIN_AUTH_PATH=/admin/authenticate?token=
|
||||
# need to change when frontend is hosted on a different domain as the backend
|
||||
WALLET_URL=$URL_PROTOCOL://$COMMUNITY_HOST
|
||||
GRAPHQL_PATH=/graphql
|
||||
|
||||
# login expire time
|
||||
JWT_SECRET=secret123
|
||||
JWT_EXPIRES_IN=10m
|
||||
|
||||
# Federation
|
||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||
# on an hash created from this topic
|
||||
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
|
||||
#
|
||||
# the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010
|
||||
FEDERATION_COMMUNITY_API_PORT=5000
|
||||
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
|
||||
|
||||
# comma separated list of api-versions, which cause starting several federation modules
|
||||
FEDERATION_COMMUNITY_APIS=1_0
|
||||
|
||||
# externe gradido services (more added in future)
|
||||
GDT_ACTIVE=false
|
||||
|
||||
AUTO_POLL_INTERVAL=30000
|
||||
|
||||
# DLT-Connector (still in develop)
|
||||
DLT_ACTIVE=false
|
||||
DLT_CONNECTOR_PORT=6010
|
||||
DLT_NODE_SERVER_PORT=8340
|
||||
DLT_NODE_SERVER_URL=$URL_PROTOCOL://$COMMUNITY_HOST/dlt
|
||||
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER=/home/gradido/.gradido
|
||||
|
||||
# used for combining a newsletter on klicktipp with this gradido community
|
||||
# if used, user will be subscribed on register and can unsubscribe in his account
|
||||
KLICKTIPP=false
|
||||
KLICKTIPP_USER=gradido_test
|
||||
KLICKTIPP_PASSWORD=secret321
|
||||
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
|
||||
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
|
||||
|
||||
# Meta data in frontend pages, important when shared via facebook or twitter or for search engines
|
||||
META_TITLE_DE="Gradido – Dein Dankbarkeitskonto"
|
||||
META_TITLE_EN="Gradido - Your gratitude account"
|
||||
@ -113,6 +100,35 @@ META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natü
|
||||
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
||||
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
||||
|
||||
# externe gradido services
|
||||
|
||||
GDT_ACTIVE=false
|
||||
GDT_API_URL=https://gdt.gradido.net
|
||||
|
||||
# DLT-Connector (still in develop)
|
||||
# verify transaction additional via gradido blockchain
|
||||
DLT_ACTIVE=false
|
||||
DLT_CONNECTOR_PORT=6010
|
||||
DLT_NODE_SERVER_PORT=8340
|
||||
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER=/home/gradido/.gradido
|
||||
|
||||
# HIERO used from dlt-connector
|
||||
HIERO_HEDERA_NETWORK=testnet
|
||||
# https://docs.hedera.com/hedera/networks/testnet/testnet-access
|
||||
HIERO_OPERATOR_ID=
|
||||
HIERO_OPERATOR_KEY=
|
||||
|
||||
# for inspector from outside of server
|
||||
DLT_NODE_SERVER_URL=$URL_PROTOCOL://$COMMUNITY_HOST/dlt
|
||||
|
||||
# used for combining a newsletter on klicktipp with this gradido community
|
||||
# if used, user will be subscribed on register and can unsubscribe in his account
|
||||
KLICKTIPP=false
|
||||
KLICKTIPP_USER=gradido_test
|
||||
KLICKTIPP_PASSWORD=secret321
|
||||
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
|
||||
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
|
||||
|
||||
# update page shown while updating gradido
|
||||
# page will be fed with status changes
|
||||
NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-page
|
||||
@ -124,24 +140,82 @@ NGINX_SSL_DHPARAM=/etc/letsencrypt/ssl-dhparams.pem
|
||||
NGINX_SSL_INCLUDE=/etc/letsencrypt/options-ssl-nginx.conf
|
||||
NGINX_REWRITE_LEGACY_URLS=false
|
||||
|
||||
# LEGACY
|
||||
WEBHOOK_ELOPAGE_SECRET=secret
|
||||
# Services
|
||||
BACKEND_PORT=4000
|
||||
|
||||
# GMS
|
||||
# Speak with Gradido Academy to get access to GMS
|
||||
GMS_ACTIVE=false
|
||||
# Coordinates of Illuminz test instance
|
||||
#GMS_API_URL=http://54.176.169.179:3071
|
||||
GMS_API_URL=http://localhost:4044/
|
||||
GMS_DASHBOARD_URL=http://localhost:8080/
|
||||
GMS_WEBHOOK_SECRET=secret
|
||||
GMS_API_URL=https://gms-stage.gradido.net/gms
|
||||
GMS_DASHBOARD_URL=https://gms-stage.gradido.net/
|
||||
GMS_USER_SEARCH_FRONTEND_ROUTE=user-search
|
||||
# set your own or placeholder webhook_secret will be replaced by start.sh
|
||||
GMS_WEBHOOK_SECRET=webhook_secret
|
||||
GMS_CREATE_USER_THROW_ERRORS=false
|
||||
|
||||
# HUMHUB
|
||||
# Host your own humhub Server
|
||||
HUMHUB_ACTIVE=false
|
||||
HUMHUB_API_URL=https://community.gradido.net
|
||||
HUMHUB_JWT_KEY=
|
||||
HUMHUB_API_URL=https://your-humhub-domain.tld
|
||||
# Humhub jwt key, setup together with humhub
|
||||
# set your own or placeholder jwt_secret will be replaced by start.sh
|
||||
HUMHUB_JWT_KEY=jwt_secret
|
||||
|
||||
# OPENAI
|
||||
# you need a paid openai account to use this feature
|
||||
OPENAI_ACTIVE=false
|
||||
OPENAI_API_KEY=''
|
||||
OPENAI_ASSISTANT_ID=asst_MF5cchZr7wY7rNXayuWvZFsM
|
||||
# assistant id for your openai assistant
|
||||
OPENAI_ASSISTANT_ID=
|
||||
# your api key form openai
|
||||
OPENAI_API_KEY=
|
||||
|
||||
# Performance Settings
|
||||
|
||||
# in milliseconds, how often inspector and frontend ask on some views for new data
|
||||
AUTO_POLL_INTERVAL=60000
|
||||
# set to true if password encryption should take place in another thread, which don't block the main loop, true is recommended
|
||||
USE_CRYPTO_WORKER=true
|
||||
# in milliseconds, how often the other communities should be checked if still online and reachable
|
||||
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
|
||||
|
||||
# Security Settings, placeholder (jwt_secret, webhook_secret, binary8_secret, binary16_secret, binary32_secret)
|
||||
# will be replaced by start.sh
|
||||
|
||||
JWT_SECRET=jwt_secret
|
||||
# secret for webhook, must be set here and in github.com webhook
|
||||
WEBHOOK_GITHUB_SECRET=webhook_secret
|
||||
# basic for key pair generation for federation, maximal 64 characters
|
||||
FEDERATION_DHT_SEED=binary32_secret
|
||||
WEBHOOK_ELOPAGE_SECRET=webhook_secret
|
||||
# keys for dlt
|
||||
GRADIDO_BLOCKCHAIN_CRYPTO_APP_SECRET=binary8_secret
|
||||
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY=binary16_secret
|
||||
HOME_COMMUNITY_SEED=binary32_secret
|
||||
|
||||
# Server Settings needed if modules are hosted on different servers (non-default setup)
|
||||
|
||||
# GUI-Module Configs (with nginx-routing always running on COMMUNITY_HOST)
|
||||
ADMIN_MODULE_PROTOCOL=$URL_PROTOCOL
|
||||
ADMIN_MODULE_HOST=$COMMUNITY_HOST
|
||||
ADMIN_MODULE_PORT=8080
|
||||
|
||||
FRONTEND_MODULE_PROTOCOL=$URL_PROTOCOL
|
||||
FRONTEND_MODULE_HOST=$COMMUNITY_HOST
|
||||
FRONTEND_MODULE_PORT=3000
|
||||
|
||||
# Non-GUI-Module Configs (with nginx-routing always running on localhost in case of on bare metal)
|
||||
BACKEND_MODULE_PROTOCOL=http
|
||||
BACKEND_MODULE_HOST=localhost
|
||||
BACKEND_PORT=4000
|
||||
|
||||
DHT_MODULE_PROTOCOL=http
|
||||
DHT_MODULE_HOST=localhost
|
||||
DHT_MODULE_PORT=5000
|
||||
|
||||
DLT_MODULE_PROTOCOL=http
|
||||
DLT_MODULE_HOST=localhost
|
||||
DLT_MODULE_PORT=6010
|
||||
|
||||
FEDERATION_MODULE_PROTOCOL=http
|
||||
FEDERATION_MODULE_HOST=localhost
|
||||
FEDERATION_MODULE_PORT=5010
|
||||
@ -40,6 +40,8 @@ ENV BRANCH_NAME=$BRANCH_NAME
|
||||
RUN git clone https://github.com/gradido/gradido.git --branch $BRANCH_NAME
|
||||
RUN cp /app/gradido/deployment/bare_metal/.env.dist /app/gradido/deployment/bare_metal/.env
|
||||
RUN sed -i 's/^URL_PROTOCOL=https$/URL_PROTOCOL=http/' /app/gradido/deployment/bare_metal/.env
|
||||
RUN sed -i 's/^COMMUNITY_HOST=gddhost.tld$/COMMUNITY_HOST=localhost/' /app/gradido/deployment/bare_metal/.env
|
||||
|
||||
|
||||
# setup nginx
|
||||
WORKDIR /app/gradido/deployment/bare_metal/nginx
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# source profile so PATH/NVM/BUN werden gesetzt (safe for non-login)
|
||||
if [ -f /home/gradido/.profile ]; then
|
||||
. /home/gradido/.profile
|
||||
fi
|
||||
# Ensure required tools are installed
|
||||
|
||||
# make sure correct node version is installed
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
if ! command -v nvm > /dev/null
|
||||
then
|
||||
echo "'nvm' is missing, will be installed now!"
|
||||
@ -14,7 +15,7 @@ install_nvm() {
|
||||
nvm install
|
||||
nvm use
|
||||
nvm alias default
|
||||
npm i -g yarn pm2
|
||||
npm i -g pm2 turbo
|
||||
pm2 startup
|
||||
}
|
||||
nvm use || install_nvm
|
||||
@ -52,23 +53,24 @@ fi
|
||||
if ! command -v turbo > /dev/null
|
||||
then
|
||||
echo "'turbo' is missing, will be installed now!"
|
||||
bun install --global turbo
|
||||
npm i -g turbo
|
||||
fi
|
||||
|
||||
# rust and grass
|
||||
if ! command -v cargo > /dev/null
|
||||
then
|
||||
echo "'cargo' is missing, will be installed now!"
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
export CARGO_HOME="$HOME/.cargo"
|
||||
export PATH="$CARGO_HOME/bin:$PATH"
|
||||
if [ "$USE_GRASS" = true ]; then
|
||||
if ! command -v cargo > /dev/null
|
||||
then
|
||||
echo "'cargo' is missing, will be installed now!"
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
export CARGO_HOME="$HOME/.cargo"
|
||||
export PATH="$CARGO_HOME/bin:$PATH"
|
||||
fi
|
||||
if ! command -v grass > /dev/null
|
||||
then
|
||||
echo "'grass' is missing, will be installed now!"
|
||||
cargo install grass
|
||||
fi
|
||||
fi
|
||||
if ! command -v grass > /dev/null
|
||||
then
|
||||
echo "'grass' is missing, will be installed now!"
|
||||
cargo install grass
|
||||
fi
|
||||
|
||||
# redis
|
||||
if ! command -v redis-cli --version > /dev/null
|
||||
then
|
||||
|
||||
@ -1,149 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This install script requires the minimum requirements already installed.
|
||||
# How to do this is described in detail in [setup.md](./setup.md)
|
||||
|
||||
# Find current directory & configure paths
|
||||
## For manualy use in terminal
|
||||
## set -o allexport
|
||||
## SCRIPT_DIR=$(pwd)
|
||||
## PROJECT_ROOT=$SCRIPT_DIR/../..
|
||||
## set +o allexport
|
||||
# Use here in script
|
||||
set -o allexport
|
||||
SCRIPT_PATH=$(realpath $0)
|
||||
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
|
||||
PROJECT_ROOT=$SCRIPT_DIR/../..
|
||||
set +o allexport
|
||||
|
||||
# Load .env or .env.dist if not present
|
||||
# NOTE: all config values will be in process.env when starting
|
||||
# the services and will therefore take precedence over the .env
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
set -o allexport
|
||||
source $SCRIPT_DIR/.env
|
||||
set +o allexport
|
||||
else
|
||||
set -o allexport
|
||||
source $SCRIPT_DIR/.env.dist
|
||||
set +o allexport
|
||||
fi
|
||||
|
||||
# Configure git
|
||||
git config pull.ff only
|
||||
|
||||
# Install mariadb
|
||||
sudo apt-get install -y mariadb-server
|
||||
sudo mysql_secure_installation
|
||||
# Enter current password for root (enter for none): enter
|
||||
# Switch to unix_socket authentication [Y/n] Y
|
||||
# Change the root password? [Y/n] n
|
||||
# Remove anonymous users? [Y/n] Y
|
||||
# Disallow root login remotely? [Y/n] Y
|
||||
# Remove test database and access to it? [Y/n] Y
|
||||
# Reload privilege tables now? [Y/n] Y
|
||||
|
||||
# Install nginx
|
||||
sudo apt-get install -y nginx
|
||||
sudo rm /etc/nginx/sites-enabled/default
|
||||
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/sites-available/gradido.conf /etc/nginx/sites-available
|
||||
# sudo ln -s /etc/nginx/sites-available/gradido.conf /etc/nginx/sites-enabled
|
||||
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/sites-available/update-page.conf /etc/nginx/sites-available
|
||||
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/common /etc/nginx/
|
||||
sudo rmdir /etc/nginx/conf.d
|
||||
sudo ln -s /home/gradido/gradido/deployment/bare_metal/nginx/conf.d /etc/nginx/
|
||||
|
||||
# Allow nginx configuration and restart for gradido
|
||||
#TODO generate file
|
||||
sudo nano /etc/sudoers.d/gradido
|
||||
> gradido ALL=(ALL) NOPASSWD: /etc/init.d/nginx start,/etc/init.d/nginx stop,/etc/init.d/nginx restart
|
||||
sudo chmod a+rw /etc/nginx/sites-enabled
|
||||
|
||||
# Install node 16.x
|
||||
sudo apt-get install -y curl
|
||||
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
# Install yarn
|
||||
sudo apt-get install -y gnupg
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y yarn
|
||||
|
||||
# Install pm2
|
||||
sudo yarn global add pm2
|
||||
pm2 startup
|
||||
> execute command output in shell
|
||||
|
||||
# Install certbot
|
||||
sudo apt-get install -y certbot
|
||||
sudo apt-get install -y python3-certbot-nginx
|
||||
sudo certbot
|
||||
> Enter email address (used for urgent renewal and security notices) > e.g. support@supportmail.com
|
||||
> Please read the Terms of Service at > Y
|
||||
> Would you be willing, once your first certificate is successfully issued, to > N
|
||||
> No names were found in your configuration files. Please enter in your domain > stage1.gradido.net
|
||||
# Note: this will throw an error regarding not beeing able to identify the nginx corresponding
|
||||
# config but produce the required certificate - thats perfectly fine this way
|
||||
# Troubleshoot: to manually renew a certificate with running nginx use the following command:
|
||||
# (this might be required once to properly have things setup for the cron to autorenew)
|
||||
# sudo certbot --nginx -d example.com -d www.example.com
|
||||
# Troubleshoot: to check ut if things working you can use
|
||||
# sudo certbot renew --dry-run
|
||||
|
||||
# Install logrotate
|
||||
sudo apt-get install -y logrotate
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/logrotate/gradido.conf.template > $SCRIPT_DIR/logrotate/gradido.conf
|
||||
sudo cp $SCRIPT_DIR/logrotate/gradido.conf.template /etc/logrotate.d/gradido.conf
|
||||
sudo chown root:root /etc/logrotate.d/gradido.conf
|
||||
|
||||
# Install mysql autobackup
|
||||
sudo apt-get install -y automysqlbackup
|
||||
|
||||
# Webhooks (optional) (for development)
|
||||
sudo apt install -y webhook
|
||||
# TODO generate
|
||||
# put hook into github
|
||||
# TODO adjust secret
|
||||
# TODO adjust branch if needed
|
||||
# https://stage1.gradido.net/hooks/github
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_DIR/webhook/hooks.json.template > ~/hooks.json
|
||||
|
||||
webhook -hooks ~/hooks.json &
|
||||
# or for debugging
|
||||
# webhook -hooks ~/hooks.json -verbose
|
||||
|
||||
# create db user
|
||||
export DB_USER=gradido
|
||||
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
|
||||
sudo mysql <<EOFMYSQL
|
||||
CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
|
||||
GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
EOFMYSQL
|
||||
|
||||
# Configure database
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
|
||||
|
||||
# Configure backend
|
||||
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
|
||||
|
||||
# Configure frontend
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env
|
||||
|
||||
# Configure admin
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
|
||||
|
||||
# Configure dht-node
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env
|
||||
|
||||
# create cronjob to delete yarn output in /tmp
|
||||
# crontab -e
|
||||
# hourly job: 0 * * * * find /tmp -name "yarn--*" -cmin +60 -exec rm -r {} \; > /dev/null
|
||||
# daily job: 0 4 * * * find /tmp -name "yarn--*" -ctime +1 -exec rm -r {} \; > /dev/null
|
||||
# Start gradido
|
||||
# Note: on first startup some errors will occur - nothing serious
|
||||
./start.sh
|
||||
@ -2,6 +2,11 @@
|
||||
# stop if something fails
|
||||
set -euo pipefail
|
||||
|
||||
# source profile so PATH/NVM/BUN werden gesetzt (safe for non-login)
|
||||
if [ -f /home/gradido/.profile ]; then
|
||||
. /home/gradido/.profile
|
||||
fi
|
||||
|
||||
# check for parameter
|
||||
FAST_MODE=false
|
||||
POSITIONAL_ARGS=()
|
||||
@ -184,7 +189,7 @@ cd $PROJECT_ROOT
|
||||
# TODO: this overfetches alot, but ensures we can use start.sh with tags
|
||||
git fetch --all
|
||||
git checkout $BRANCH_NAME
|
||||
git pull
|
||||
git pull origin $BRANCH_NAME
|
||||
git submodule update --init --recursive
|
||||
export BUILD_COMMIT="$(git rev-parse HEAD)"
|
||||
|
||||
@ -299,7 +304,7 @@ done
|
||||
|
||||
# Install all node_modules
|
||||
log_step 'Installing node_modules'
|
||||
bun install
|
||||
bun install --frozen-lockfile
|
||||
|
||||
# build all modules
|
||||
log_step 'build all modules'
|
||||
@ -309,12 +314,12 @@ turbo build --env-mode=loose --concurrency=$(nproc)
|
||||
if [ "$DLT_ACTIVE" = true ]; then
|
||||
log_step 'build inspector'
|
||||
cd $PROJECT_ROOT/inspector
|
||||
bun install
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
log_step 'build dlt-connector'
|
||||
cd $PROJECT_ROOT/dlt-connector
|
||||
bun install
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
cd $PROJECT_ROOT
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
# Migration
|
||||
[Migration from 2.2.0 to 2.2.1](migration/2_2_0-2_2_1/README.md)
|
||||
|
||||
# Key Pair
|
||||
It is recommended to create a new ssh key pair for your gradido server.
|
||||
You can create it with this command:
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "your_email@example.com"
|
||||
```
|
||||
|
||||
**Reason**: We recommend `ed25519` because it provides strong security with smaller key sizes, faster performance, and resistance to known attacks, making it more secure and efficient than traditional RSA keys.
|
||||
|
||||
# Setup on Hetzner Cloud Server
|
||||
Suggested OS:
|
||||
Debian 12
|
||||
@ -15,7 +24,7 @@ ssh_authorized_keys:
|
||||
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkLGbzbG7KIGfkssKJBkc/0EVAzQ/8vjvVHzNdxhK8J yourname
|
||||
```
|
||||
|
||||
I made a (german) video to show it to you:
|
||||
I made a (german) video to show it to you (video is older, cloudConfig.yaml differ):
|
||||
|
||||
[](https://www.youtube.com/watch?v=fORK3Bt3lPw)
|
||||
|
||||
@ -23,14 +32,7 @@ I made a (german) video to show it to you:
|
||||
### setup your domain pointing on server ip address
|
||||
### login to your new server as root
|
||||
```bash
|
||||
ssh -i /path/to/privKey root@gddhost.tld
|
||||
```
|
||||
|
||||
### Change default shell
|
||||
|
||||
```bash
|
||||
chsh -s /bin/bash
|
||||
chsh -s /bin/bash gradido
|
||||
ssh -i ~/.ssh/id_ed25519 root@gddhost.tld
|
||||
```
|
||||
|
||||
### Set password for user `gradido`
|
||||
@ -51,16 +53,15 @@ su gradido
|
||||
If you logout from the server you can test authentication:
|
||||
|
||||
```bash
|
||||
$ ssh -i /path/to/privKey gradido@gddhost.tld
|
||||
$ ssh -i ~/.ssh/id_ed25519 gradido@gddhost.tld
|
||||
# This should log you in and allow you to use sudo commands, which will require the user's password
|
||||
```
|
||||
|
||||
### Disable password root login via ssh
|
||||
|
||||
```bash
|
||||
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.org
|
||||
sudo sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
||||
sudo sed -i '$a AllowUsers gradido' /etc/ssh/sshd_config
|
||||
sudo sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config.d/ssh-hardening.conf
|
||||
sudo sed -i '$a AllowUsers gradido' /etc/ssh/sshd_config.d/ssh-hardening.conf
|
||||
sudo /etc/init.d/ssh restart
|
||||
```
|
||||
|
||||
@ -69,16 +70,17 @@ sudo /etc/init.d/ssh restart
|
||||
```bash
|
||||
$ ssh gradido@gddhost.tld
|
||||
# Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)'
|
||||
$ ssh -i /path/to/privKey root@gddhost.tld
|
||||
$ ssh -i ~/.ssh/id_ed25519 root@gddhost.tld
|
||||
# Will result in 'Permission denied (publickey)'
|
||||
$ ssh -i /path/to/privKey gradido@gddhost.tld
|
||||
$ ssh -i ~/.ssh/id_ed25519 gradido@gddhost.tld
|
||||
# Will succeed after entering the correct keys passphrase (if any)
|
||||
```
|
||||
|
||||
### Install `Gradido` code
|
||||
`latest` is a tag pointing on last stable release
|
||||
```bash
|
||||
cd ~
|
||||
git clone https://github.com/gradido/gradido.git
|
||||
git clone https://github.com/gradido/gradido.git --branch latest --depth 1
|
||||
```
|
||||
|
||||
### Adjust the values in `.env`
|
||||
@ -99,21 +101,35 @@ All your following installations in `install.sh` will fail!*
|
||||
cd ~/gradido/deployment/bare_metal
|
||||
cp .env.dist .env
|
||||
nano .env
|
||||
|
||||
# adjust values accordingly
|
||||
```
|
||||
|
||||
### Run `install.sh` with branch name
|
||||
For a minimal setup you need at least to change this values:
|
||||
```env
|
||||
COMMUNITY_NAME="Your community name"
|
||||
COMMUNITY_DESCRIPTION="Short Description from your Community."
|
||||
# your domain name, without protocol (without https:// or http:// )
|
||||
# domain name should be configured in your dns records to point to this server
|
||||
# hetzner_cloud/install.sh will be acquire a SSL-certificate via letsencrypt for this domain
|
||||
COMMUNITY_HOST=gddhost.tld
|
||||
|
||||
# setup email account for sending gradido system messages to users
|
||||
EMAIL_USERNAME=peter@lustig.de
|
||||
EMAIL_SENDER=peter@lustig.de
|
||||
EMAIL_PASSWORD=1234
|
||||
EMAIL_SMTP_HOST=smtp.lustig.de
|
||||
```
|
||||
|
||||
### Run `install.sh` with branch or tag name
|
||||
***!!! Attention !!!***
|
||||
Don't use this script if you have custom config in /etc/nginx/conf.d, because this script
|
||||
will remove it and ln ../bare_metal/nginx/conf.d
|
||||
|
||||
```bash
|
||||
cd ~/gradido/deployment/hetzner_cloud
|
||||
sudo ./install.sh release-2_2_0
|
||||
sudo ./install.sh latest
|
||||
```
|
||||
|
||||
I made a (german) video to show it to you:
|
||||
I made a (german) video to show it to you (video is older, output will differ):
|
||||
|
||||
[](https://www.youtube.com/watch?v=9h-55Si6bMk)
|
||||
|
||||
@ -133,3 +149,16 @@ sudo mysql -D gradido_community -e "insert into user_roles(user_id, role) values
|
||||
I made a (german) video to show it to you:
|
||||
|
||||
[](https://www.youtube.com/watch?v=xVQ5t4MnLrE)
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If after some tests this error occur, right after `Requesting a certificate for your-domain.tld`, try again another day. Letsencrypt is rate limited:
|
||||
|
||||
```bash
|
||||
An unexpected error occurred:
|
||||
AttributeError: can't set attribute
|
||||
```
|
||||
|
||||
### But it isn't working
|
||||
|
||||
If it isn't working you can write us: [support@gradido.net](mailto:support@gradido.net)
|
||||
|
||||
@ -14,6 +14,7 @@ packages:
|
||||
- git
|
||||
- mariadb-server
|
||||
- nginx
|
||||
- redis
|
||||
- curl
|
||||
- build-essential
|
||||
- gnupg
|
||||
@ -22,6 +23,7 @@ packages:
|
||||
- logrotate
|
||||
- automysqlbackup
|
||||
- expect
|
||||
- unzip
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
write_files:
|
||||
|
||||
@ -1,7 +1,28 @@
|
||||
#!/bin/bash
|
||||
# stop if something fails
|
||||
set -euo pipefail
|
||||
|
||||
log_error() {
|
||||
local message="$1"
|
||||
echo -e "\e[31m$message\e[0m" # red in console
|
||||
}
|
||||
|
||||
# called always on error, log error really visible with ascii art in red on console and html
|
||||
# stop script execution
|
||||
onError() {
|
||||
local exit_code=$?
|
||||
log_error "Command failed!"
|
||||
log_error " /\\_/\\ Line: $(caller 0)"
|
||||
log_error "( x.x ) Exit Code: $exit_code"
|
||||
log_error " > < Offending command: '$BASH_COMMAND'"
|
||||
log_error ""
|
||||
exit 1
|
||||
}
|
||||
trap onError ERR
|
||||
|
||||
# check for parameter
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: Please provide a branch name as the first argument."
|
||||
log_error "Usage: Please provide a branch name as the first argument."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -19,25 +40,88 @@ LOCAL_SCRIPT_DIR=$(dirname $LOCAL_SCRIPT_PATH)
|
||||
PROJECT_ROOT=$SCRIPT_DIR/..
|
||||
set +o allexport
|
||||
|
||||
# Replace placeholder secrets in .env
|
||||
echo 'Replace placeholder secrets in .env'
|
||||
# Load .env or .env.dist if not present
|
||||
# NOTE: all config values will be in process.env when starting
|
||||
# the services and will therefore take precedence over the .env
|
||||
if [ -f "$SCRIPT_PATH/.env" ]; then
|
||||
ENV_FILE="$SCRIPT_PATH/.env"
|
||||
|
||||
# --- Secret Generators -------------------------------------------------------
|
||||
|
||||
gen_jwt_secret() {
|
||||
# 32 Character, URL-safe: A-Z a-z 0-9 _ -
|
||||
tr -dc 'A-Za-z0-9_-' < /dev/urandom | head -c 32 2>/dev/null || true
|
||||
}
|
||||
|
||||
gen_webhook_secret() {
|
||||
# URL-safe, longer security (40 chars)
|
||||
tr -dc 'A-Za-z0-9_-' < /dev/urandom | head -c 40 2>/dev/null || true
|
||||
}
|
||||
|
||||
gen_binary_secret() {
|
||||
local bytes="$1"
|
||||
# Hex -> 2 chars pro byte
|
||||
openssl rand -hex "$bytes" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# --- Mapping of Placeholder -> Function --------------------------------------
|
||||
|
||||
generate_secret_for() {
|
||||
case "$1" in
|
||||
jwt_secret) gen_jwt_secret ;;
|
||||
webhook_secret) gen_webhook_secret ;;
|
||||
binary8_secret) gen_binary_secret 8 ;;
|
||||
binary16_secret) gen_binary_secret 16;;
|
||||
binary32_secret) gen_binary_secret 32;;
|
||||
*)
|
||||
echo "Unknown Placeholder: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# --- Placeholder List --------------------------------------------------------
|
||||
|
||||
placeholders=(
|
||||
"jwt_secret"
|
||||
"webhook_secret"
|
||||
"binary8_secret"
|
||||
"binary16_secret"
|
||||
"binary32_secret"
|
||||
)
|
||||
|
||||
# --- Processing in .env -------------------------------------------------
|
||||
|
||||
TMP_FILE="${ENV_FILE}.tmp"
|
||||
cp "$ENV_FILE" "$TMP_FILE"
|
||||
|
||||
for ph in "${placeholders[@]}"; do
|
||||
# Iterate over all lines containing the placeholder
|
||||
while grep -q "$ph" "$TMP_FILE"; do
|
||||
new_value=$(generate_secret_for "$ph")
|
||||
# Replace only the first occurrence per line
|
||||
sed -i "0,/$ph/s//$new_value/" "$TMP_FILE"
|
||||
done
|
||||
done
|
||||
|
||||
# Write back
|
||||
mv "$TMP_FILE" "$ENV_FILE"
|
||||
chown gradido:gradido "$ENV_FILE"
|
||||
fi
|
||||
|
||||
# If install.sh will be called more than once
|
||||
# We have to load the backend .env to get DB_USERNAME, DB_PASSWORD AND JWT_SECRET
|
||||
# and the dht-node .env to get FEDERATION_DHT_SEED
|
||||
# We have to load the backend .env to get DB_USERNAME and DB_PASSWORD
|
||||
export_var(){
|
||||
export $1=$(grep -v '^#' $PROJECT_ROOT/backend/.env | grep -e "$1" | sed -e 's/.*=//')
|
||||
export $1=$(grep -v '^#' $PROJECT_ROOT/dht-node/.env | grep -e "$1" | sed -e 's/.*=//')
|
||||
}
|
||||
|
||||
if [ -f "$PROJECT_ROOT/backend/.env" ]; then
|
||||
export_var 'DB_USER'
|
||||
export_var 'DB_PASSWORD'
|
||||
export_var 'JWT_SECRET'
|
||||
fi
|
||||
|
||||
if [ -f "$PROJECT_ROOT/dht-node/.env" ]; then
|
||||
export_var 'FEDERATION_DHT_SEED'
|
||||
fi
|
||||
|
||||
|
||||
# Load .env or .env.dist if not present
|
||||
# NOTE: all config values will be in process.env when starting
|
||||
# the services and will therefore take precedence over the .env
|
||||
@ -97,15 +181,17 @@ systemctl restart fail2ban
|
||||
rm /etc/nginx/sites-enabled/default
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/gradido.conf.template > $SCRIPT_PATH/nginx/sites-available/gradido.conf
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/update-page.conf.template > $SCRIPT_PATH/nginx/sites-available/update-page.conf
|
||||
mkdir $SCRIPT_PATH/nginx/sites-enabled
|
||||
ln -s $SCRIPT_PATH/nginx/sites-available/update-page.conf $SCRIPT_PATH/nginx/sites-enabled/default
|
||||
ln -s $SCRIPT_PATH/nginx/sites-enabled/default /etc/nginx/sites-enabled
|
||||
ln -s $SCRIPT_PATH/nginx/common /etc/nginx/
|
||||
rmdir /etc/nginx/conf.d
|
||||
ln -s $SCRIPT_PATH/nginx/conf.d /etc/nginx/
|
||||
mkdir -p $SCRIPT_PATH/nginx/sites-enabled
|
||||
ln -sf $SCRIPT_PATH/nginx/sites-available/update-page.conf $SCRIPT_PATH/nginx/sites-enabled/default
|
||||
ln -sf $SCRIPT_PATH/nginx/sites-enabled/default /etc/nginx/sites-enabled
|
||||
ln -sf $SCRIPT_PATH/nginx/common /etc/nginx/
|
||||
if [ -e /etc/nginx/conf.d ] && [ ! -L /etc/nginx/conf.d ]; then
|
||||
rm -rf /etc/nginx/conf.d
|
||||
ln -s $SCRIPT_PATH/nginx/conf.d /etc/nginx/
|
||||
fi
|
||||
|
||||
# Make nginx restart automatic
|
||||
mkdir /etc/systemd/system/nginx.service.d
|
||||
mkdir -p /etc/systemd/system/nginx.service.d
|
||||
# Define the content to be put into the override.conf file
|
||||
CONFIG_CONTENT="[Unit]
|
||||
StartLimitIntervalSec=500
|
||||
@ -124,11 +210,17 @@ sudo systemctl daemon-reload
|
||||
# setup https with certbot
|
||||
certbot certonly --nginx --non-interactive --agree-tos --domains $COMMUNITY_HOST --email $COMMUNITY_SUPPORT_MAIL
|
||||
|
||||
export NVM_DIR="/home/gradido/.nvm"
|
||||
BUN_VERSION_FILE="$PROJECT_ROOT/.bun-version"
|
||||
if [ ! -f "$BUN_VERSION_FILE" ]; then
|
||||
echo ".bun-version file not found at: $BUN_VERSION_FILE"
|
||||
exit 1
|
||||
fi
|
||||
export BUN_VERSION="$(cat "$BUN_VERSION_FILE" | tr -d '[:space:]')"
|
||||
export BUN_INSTALL="/home/gradido/.bun"
|
||||
|
||||
# run as gradido user (until EOF)
|
||||
sudo -u gradido bash <<'EOF'
|
||||
export NVM_DIR="/home/gradido/.nvm"
|
||||
NODE_VERSION="v18.20.7"
|
||||
export NVM_DIR
|
||||
sudo -u gradido bash <<EOF
|
||||
# Install nvm if it doesn't exist
|
||||
if [ ! -d "$NVM_DIR" ]; then
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
||||
@ -137,15 +229,26 @@ sudo -u gradido bash <<'EOF'
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
|
||||
# Install Node if not already installed
|
||||
if ! nvm ls $NODE_VERSION >/dev/null 2>&1; then
|
||||
nvm install $NODE_VERSION
|
||||
if ! nvm ls >/dev/null 2>&1; then
|
||||
nvm install
|
||||
fi
|
||||
# Install yarn and pm2
|
||||
npm i -g yarn pm2
|
||||
# start pm2
|
||||
pm2 startup
|
||||
# Install pm2 and turbo
|
||||
npm i -g pm2 turbo
|
||||
|
||||
echo "'bun' v$BUN_VERSION will be installed now!"
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
EOF
|
||||
|
||||
# Load bun
|
||||
export BUN_INSTALL="/home/gradido/.bun"
|
||||
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||
|
||||
# Load nvm
|
||||
export NVM_DIR="/home/gradido/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
# start pm2
|
||||
pm2 startup
|
||||
|
||||
# Install logrotate
|
||||
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/logrotate/gradido.conf.template > $SCRIPT_PATH/logrotate/gradido.conf
|
||||
cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
|
||||
@ -153,15 +256,15 @@ cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
|
||||
# create db user
|
||||
export DB_USER=gradido
|
||||
# create a new password only if it not already exist
|
||||
if [ -z "${DB_PASSWORD}" ]; then
|
||||
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c 32; echo);
|
||||
fi
|
||||
: "${DB_PASSWORD:=$(tr -dc '_A-Za-z0-9' < /dev/urandom | head -c 32)}"
|
||||
|
||||
# Check if DB_PASSWORD is still empty, then exit with an error
|
||||
if [ -z "${DB_PASSWORD}" ]; then
|
||||
echo "Error: Failed to generate DB_PASSWORD."
|
||||
exit 1
|
||||
fi
|
||||
export DB_PASSWORD
|
||||
|
||||
mysql <<EOFMYSQL
|
||||
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
|
||||
GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost';
|
||||
@ -195,5 +298,4 @@ chown -R gradido:gradido $PROJECT_ROOT
|
||||
sudo -u gradido crontab < $LOCAL_SCRIPT_DIR/crontabs.txt
|
||||
|
||||
# Start gradido
|
||||
# Note: on first startup some errors will occur - nothing serious
|
||||
sudo -u gradido $SCRIPT_PATH/start.sh $1
|
||||
@ -1,6 +1,3 @@
|
||||
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
|
||||
CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
||||
|
||||
# Database
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
@ -14,7 +11,6 @@ COMMUNITY_NAME=$COMMUNITY_NAME
|
||||
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||
|
||||
# Federation
|
||||
FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
|
||||
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
|
||||
# on an hash created from this topic
|
||||
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dht-node",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido dht-node module",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/",
|
||||
|
||||
@ -37,7 +37,7 @@ export const configSchema = v.object({
|
||||
GRADIDO_BLOCKCHAIN_SERVER_CRYPTO_KEY: hex16Schema,
|
||||
HOME_COMMUNITY_SEED: v.pipe(
|
||||
hexSchema,
|
||||
v.length(64, 'expect seed length minimum 64 characters (32 Bytes)'),
|
||||
v.length(64, 'expect seed length 64 characters (32 Bytes)'),
|
||||
v.transform<string, MemoryBlock>((input: string) => MemoryBlock.fromHex(input)),
|
||||
),
|
||||
HIERO_HEDERA_NETWORK: v.optional(
|
||||
|
||||
@ -0,0 +1,273 @@
|
||||
<mxfile host="Electron" modified="2025-12-18T01:25:36.977Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.3 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="ulUa-DwD6iMx1WLL9hxg" version="22.0.3" type="device">
|
||||
<diagram name="Seite-1" id="pXcQQGq2mbEeDNBOBd4l">
|
||||
<mxGraphModel dx="1206" dy="702" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-4" value="recepient: backend:<br>TransactionLinkResolver" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="40" width="140" height="920" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-5" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="70" width="10" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-6" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">queryTransactionLink</div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-5" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="-0.2381" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="75" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-7" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="160" width="10" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-8" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">queryRedeemJwtLink</div>" style="html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;edgeStyle=orthogonalEdgeStyle;curved=0;rounded=0;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-7" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="70" y="140" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="100" y="170" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-11" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="230" width="10" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-12" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">redeemTransactionLink</div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-11" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="-0.4286" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="235" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-13" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-4">
|
||||
<mxGeometry x="65" y="393" width="10" height="367" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-14" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;"><div style="line-height: 19px;">disburseTransactionLink</div></div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="EPUhzRXaTLY6ULSqPNZg-4" target="EPUhzRXaTLY6ULSqPNZg-13">
|
||||
<mxGeometry x="-0.4286" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="398" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-9" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">TransactionLink</div>" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="140" y="170" as="sourcePoint" />
|
||||
<mxPoint x="40" y="170" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-10" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">RedeemJwtLink</div>" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="140" y="230" as="sourcePoint" />
|
||||
<mxPoint x="40" y="230" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-15" value="backend:<br>:TransactionReslover" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="40" width="120" height="920" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-16" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-15">
|
||||
<mxGeometry x="55" y="290" width="10" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-17" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">executeTransaction</div>" style="html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-16" parent="1" source="EPUhzRXaTLY6ULSqPNZg-11">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="265" y="335" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-18" value="sender: federation:<br>:DisbursementResolver" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="40" width="130" height="920" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-19" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-18">
|
||||
<mxGeometry x="60" y="400" width="10" height="320" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-20" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processDisburseJwtOnSenderCommunity</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-19" parent="1" source="EPUhzRXaTLY6ULSqPNZg-13">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="430" y="445" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-22" value="sender: core:<br>processXComSendCoins" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="570" y="40" width="140" height="1080" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-23" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry x="65" y="410" width="10" height="640" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-26" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry x="68" y="435" width="10" height="225" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-27" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processXComPendingSendCoins</div>" style="html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;edgeStyle=orthogonalEdgeStyle;curved=0;rounded=0;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-26" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="70" y="420" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="100" y="420" />
|
||||
<mxPoint x="100" y="450" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-43" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry x="68" y="710" width="10" height="270" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-44" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processXComCommittingSendCoins</div>" style="html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;edgeStyle=orthogonalEdgeStyle;curved=0;rounded=0;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-43" parent="EPUhzRXaTLY6ULSqPNZg-22">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="73" y="690" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="103" y="720" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-24" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">processXComCompleteTransaction</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-23" parent="1" source="EPUhzRXaTLY6ULSqPNZg-19">
|
||||
<mxGeometry x="-0.027" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="595" y="455" as="sourcePoint" />
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-28" value="recepient: federation:<br>SendCoinsResolver" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="840" y="40" width="120" height="1080" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-29" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-28">
|
||||
<mxGeometry x="55" y="488" width="10" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-30" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">voteForSendCoins</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-29" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="533" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-32" value="recepient: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="960" y="480" width="120" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-33" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-32">
|
||||
<mxGeometry x="55" y="58" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-35" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" source="EPUhzRXaTLY6ULSqPNZg-33" parent="1" target="EPUhzRXaTLY6ULSqPNZg-29">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="945" y="613" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-34" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" target="EPUhzRXaTLY6ULSqPNZg-33" parent="1" source="EPUhzRXaTLY6ULSqPNZg-29">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="945" y="543" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-31" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" source="EPUhzRXaTLY6ULSqPNZg-29" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="573" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-36" value="sender: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="715" y="588" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-37" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-36">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-38" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-37">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="673" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-39" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-37">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="653" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-21" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" source="EPUhzRXaTLY6ULSqPNZg-19" parent="1" target="EPUhzRXaTLY6ULSqPNZg-13">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="370" y="515" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-41" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-23">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="690" as="sourcePoint" />
|
||||
<mxPoint x="740" y="690" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="670" y="690" />
|
||||
<mxPoint x="670" y="720" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-46" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="895" y="788" width="10" height="132" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-47" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;"><div style="line-height: 19px;">settleSendCoins</div></div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-46">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="793" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-48" value="recepient: database:<br>Transaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="1210" y="740" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-49" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-48">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-50" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-49" target="EPUhzRXaTLY6ULSqPNZg-58">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1153" y="823" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-51" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;exitX=1;exitY=0;exitDx=0;exitDy=5;exitPerimeter=0;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-59" target="EPUhzRXaTLY6ULSqPNZg-49">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1153" y="803" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-52" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="910" as="targetPoint" />
|
||||
<mxPoint x="895" y="910" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-53" value="sender: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="715" y="920" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-54" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-53">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-55" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-54" target="EPUhzRXaTLY6ULSqPNZg-43">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="995" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-56" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">insert</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-54" source="EPUhzRXaTLY6ULSqPNZg-43">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="975" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-57" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="648" y="1012" as="sourcePoint" />
|
||||
<mxPoint x="645" y="1042" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="670" y="1012" />
|
||||
<mxPoint x="670" y="1042" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-58" value="recepient: federation:<br>settlePendingReceiveTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="990" y="740" width="200" height="220" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-59" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-58">
|
||||
<mxGeometry x="95" y="60" width="10" height="110" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-60" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-59" target="EPUhzRXaTLY6ULSqPNZg-28">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="905" y="823" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-61" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;"><div style="line-height: 19px;">settlePendingReceiveTransaction</div></div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;" edge="1" parent="1" target="EPUhzRXaTLY6ULSqPNZg-59">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="905" y="803" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-62" value="recepient: database:<br>PendingTransaction" style="shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;dropTarget=0;collapsible=0;recursiveResize=0;outlineConnect=0;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="1">
|
||||
<mxGeometry x="1300" y="810" width="120" height="100" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-63" value="" style="html=1;points=[[0,0,0,0,5],[0,1,0,0,-5],[1,0,0,0,5],[1,1,0,0,-5]];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;newEdgeStyle={"curved":0,"rounded":0};" vertex="1" parent="EPUhzRXaTLY6ULSqPNZg-62">
|
||||
<mxGeometry x="55" y="60" width="10" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-64" value="return" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;curved=0;rounded=0;exitX=0;exitY=1;exitDx=0;exitDy=-5;exitPerimeter=0;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-63" target="EPUhzRXaTLY6ULSqPNZg-59">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1160" y="907.5" as="targetPoint" />
|
||||
<mxPoint x="1335" y="907.5" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="EPUhzRXaTLY6ULSqPNZg-65" value="<div style="color: rgb(59, 59, 59); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; font-size: 10px;">settled</div>" style="html=1;verticalAlign=bottom;endArrow=block;curved=0;rounded=0;entryX=0;entryY=0;entryDx=0;entryDy=5;entryPerimeter=0;" edge="1" parent="1" source="EPUhzRXaTLY6ULSqPNZg-59" target="EPUhzRXaTLY6ULSqPNZg-63">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="1165" y="887.5" as="sourcePoint" />
|
||||
<mxPoint x="1335" y="887.5" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@ -1,6 +1,3 @@
|
||||
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
|
||||
CONFIG_VERSION=$FEDERATION_CONFIG_VERSION
|
||||
|
||||
LOG_LEVEL=$LOG_LEVEL
|
||||
# this is set fix to false, because it is important for 'production' environments. only set to true if a graphql-playground should be in use
|
||||
GRAPHIQL=false
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "federation",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/federation",
|
||||
|
||||
17
federation/src/graphql/api/1_0/resolver/CommandResolver.ts
Normal file
17
federation/src/graphql/api/1_0/resolver/CommandResolver.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { CommandExecutor, CommandResult, EncryptedTransferArgs } from 'core'
|
||||
import { Arg, Ctx, Mutation, Resolver } from 'type-graphql'
|
||||
|
||||
@Resolver()
|
||||
export class CommandResolver {
|
||||
private commandExecutor = new CommandExecutor()
|
||||
|
||||
@Mutation(() => CommandResult)
|
||||
async sendCommand(
|
||||
@Arg('encryptedArgs', () => EncryptedTransferArgs) encryptedArgs: any,
|
||||
@Ctx() context: any,
|
||||
): Promise<CommandResult> {
|
||||
// Convert to EncryptedTransferArgs if needed
|
||||
const result = await this.commandExecutor.executeEncryptedCommand(encryptedArgs)
|
||||
return result as unknown as CommandResult
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import { NonEmptyArray } from 'type-graphql'
|
||||
import { AuthenticationResolver } from './resolver/AuthenticationResolver'
|
||||
import { CommandResolver } from './resolver/CommandResolver'
|
||||
import { DisbursementResolver } from './resolver/DisbursementResolver'
|
||||
import { PublicCommunityInfoResolver } from './resolver/PublicCommunityInfoResolver'
|
||||
import { PublicKeyResolver } from './resolver/PublicKeyResolver'
|
||||
@ -8,6 +9,7 @@ import { SendCoinsResolver } from './resolver/SendCoinsResolver'
|
||||
export const getApiResolvers = (): NonEmptyArray<Function> => {
|
||||
return [
|
||||
AuthenticationResolver,
|
||||
CommandResolver,
|
||||
DisbursementResolver,
|
||||
PublicCommunityInfoResolver,
|
||||
PublicKeyResolver,
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { NonEmptyArray } from 'type-graphql'
|
||||
import { AuthenticationResolver } from '../1_0/resolver/AuthenticationResolver'
|
||||
import { CommandResolver } from '../1_0/resolver/CommandResolver'
|
||||
import { PublicCommunityInfoResolver } from '../1_0/resolver/PublicCommunityInfoResolver'
|
||||
import { SendCoinsResolver } from '../1_0/resolver/SendCoinsResolver'
|
||||
import { PublicKeyResolver } from './resolver/PublicKeyResolver'
|
||||
|
||||
export const getApiResolvers = (): NonEmptyArray<Function> => {
|
||||
return [AuthenticationResolver, PublicCommunityInfoResolver, PublicKeyResolver, SendCoinsResolver]
|
||||
return [
|
||||
AuthenticationResolver,
|
||||
CommandResolver,
|
||||
PublicCommunityInfoResolver,
|
||||
PublicKeyResolver,
|
||||
SendCoinsResolver,
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'source-map-support/register'
|
||||
|
||||
import { defaultCategory, initLogger } from 'config-schema'
|
||||
import { initializeCommands } from 'core'
|
||||
import { getLogger } from 'log4js'
|
||||
import { onShutdown, printServerCrashAsciiArt, ShutdownReason } from 'shared'
|
||||
// config
|
||||
@ -44,6 +45,8 @@ async function main() {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
initializeCommands()
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
CONFIG_VERSION=$FRONTEND_CONFIG_VERSION
|
||||
|
||||
# Endpoints
|
||||
GRAPHQL_PATH=$GRAPHQL_PATH
|
||||
ADMIN_AUTH_PATH=$ADMIN_AUTH_PATH
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "concurrently \"yarn watch-scss\" \"vite\"",
|
||||
@ -16,8 +16,8 @@
|
||||
"test:coverage": "cross-env TZ=UTC vitest run --coverage",
|
||||
"test:debug": "cross-env TZ=UTC node --inspect-brk ./node_modules/vitest/vitest.mjs",
|
||||
"test:watch": "cross-env TZ=UTC vitest",
|
||||
"locales": "scripts/sort.sh",
|
||||
"locales:fix": "scripts/sort.sh --fix",
|
||||
"locales": "bun scripts/sortLocales.ts",
|
||||
"locales:fix": "bun scripts/sortLocales.ts --fix",
|
||||
"compile-scss": "node ./scripts/scss.mjs compile",
|
||||
"watch-scss": "node ./scripts/scss.mjs watch",
|
||||
"compile-scss-sass": "node ./scripts/scss.mjs compile sass",
|
||||
|
||||
51
frontend/scripts/sortLocales.ts
Normal file
51
frontend/scripts/sortLocales.ts
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bun
|
||||
import { readdir, readFile, writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
|
||||
const ROOT_DIR = join(import.meta.dir, '..')
|
||||
const LOCALES_DIR = join(ROOT_DIR, 'src', 'locales')
|
||||
|
||||
const FIX = process.argv.includes('--fix')
|
||||
|
||||
function sortObject(value: any): any {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(sortObject)
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.keys(value)
|
||||
.sort()
|
||||
.reduce<Record<string, any>>((acc, key) => {
|
||||
acc[key] = sortObject(value[key])
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
let exitCode = 0
|
||||
|
||||
const files = (await readdir(LOCALES_DIR))
|
||||
.filter(f => f.endsWith('.json'))
|
||||
|
||||
for (const file of files) {
|
||||
const path = join(LOCALES_DIR, file)
|
||||
|
||||
const originalText = await readFile(path, 'utf8')
|
||||
const originalJson = JSON.parse(originalText)
|
||||
|
||||
const sortedJson = sortObject(originalJson)
|
||||
const sortedText = JSON.stringify(sortedJson, null, 2) + '\n'
|
||||
|
||||
if (originalText !== sortedText) {
|
||||
if (FIX) {
|
||||
await writeFile(path, sortedText)
|
||||
} else {
|
||||
console.error(`${file} is not sorted by keys`)
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(exitCode)
|
||||
@ -126,6 +126,8 @@ const form = reactive({ ...entityDataToForm.value })
|
||||
|
||||
const now = ref(new Date()) // checked every minute, updated if day, month or year changed
|
||||
const disableSmartValidState = ref(false)
|
||||
// set to true after submit, to disable submit button
|
||||
const submitted = ref(false)
|
||||
|
||||
const minimalDate = computed(() => useMinimalContributionDate(now.value))
|
||||
const isThisMonth = computed(() => {
|
||||
@ -195,7 +197,7 @@ const validationSchema = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const disabled = computed(() => !validationSchema.value.isValidSync(form))
|
||||
const disabled = computed(() => !validationSchema.value.isValidSync(form) || submitted.value)
|
||||
|
||||
// decide message if no open creation exists
|
||||
const noOpenCreation = computed(() => {
|
||||
@ -243,6 +245,7 @@ const updateField = (newValue, name) => {
|
||||
}
|
||||
|
||||
function submit() {
|
||||
submitted.value = true
|
||||
emit('upsert-contribution', toRaw(form))
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
{
|
||||
"85": "85%",
|
||||
"100": "100%",
|
||||
"125": "125%",
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 Dank, weil du bei uns bist!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"Chat": "Chat",
|
||||
"GDD": "GDD",
|
||||
"GDD-long": "Gradido",
|
||||
"GDT": "GDT",
|
||||
"GMS": {
|
||||
"title": "Geo Matching System GMS (in Entwicklung)",
|
||||
"desc": "Finde Mitglieder aller Communities auf einer Landkarte."
|
||||
"desc": "Finde Mitglieder aller Communities auf einer Landkarte.",
|
||||
"title": "Geo Matching System GMS (in Entwicklung)"
|
||||
},
|
||||
"Humhub": {
|
||||
"title": "Gradido-Kreise",
|
||||
"desc": "Gemeinsam unterstützen wir einander – achtsam in Kreiskultur."
|
||||
"desc": "Gemeinsam unterstützen wir einander – achtsam in Kreiskultur.",
|
||||
"title": "Gradido-Kreise"
|
||||
},
|
||||
"PersonalDetails": "Persönliche Angaben",
|
||||
"advanced-calculation": "Vorausberechnung",
|
||||
@ -34,27 +34,27 @@
|
||||
},
|
||||
"back": "Zurück",
|
||||
"card-circles": {
|
||||
"headline": "Kooperationsplattform »Gradido-Kreise«",
|
||||
"text": "Lokale Kreise, Studienkreise, Projekte, Events und Kongresse",
|
||||
"allowed": {
|
||||
"button": "Kreise öffnen..."
|
||||
},
|
||||
"headline": "Kooperationsplattform »Gradido-Kreise«",
|
||||
"not-allowed": {
|
||||
"button": "Konfigurieren..."
|
||||
}
|
||||
},
|
||||
"text": "Lokale Kreise, Studienkreise, Projekte, Events und Kongresse"
|
||||
},
|
||||
"card-user-search": {
|
||||
"headline": "Geografische Mitgliedssuche (beta)",
|
||||
"allowed": {
|
||||
"button": "Mitgliedssuche öffnen...",
|
||||
"disabled-button": "GMS offline...",
|
||||
"text": "Finde Mitglieder aller Communities auf einer Landkarte."
|
||||
},
|
||||
"headline": "Geografische Mitgliedssuche (beta)",
|
||||
"info": "So gehts",
|
||||
"not-allowed": {
|
||||
"button": "Standort eintragen...",
|
||||
"text": "Um andere Mitglieder in deinem Umkreis zu finden, trage jetzt deinen Standort auf der Karte ein!"
|
||||
},
|
||||
"info": "So gehts"
|
||||
}
|
||||
},
|
||||
"community": {
|
||||
"admins": "Administratoren",
|
||||
@ -95,8 +95,8 @@
|
||||
"lastContribution": "Letzte Beiträge",
|
||||
"noContributions": {
|
||||
"allContributions": "Es wurden noch keine Beiträge eingereicht.",
|
||||
"myContributions": "Du hast noch keine Beiträge eingereicht.",
|
||||
"emptyPage": "Diese Seite ist leer."
|
||||
"emptyPage": "Diese Seite ist leer.",
|
||||
"myContributions": "Du hast noch keine Beiträge eingereicht."
|
||||
},
|
||||
"noOpenCreation": {
|
||||
"allMonth": "Für alle beiden Monate ist dein Schöpfungslimit erreicht. Den Nächsten Monat kannst du wieder 1000 GDD Schöpfen.",
|
||||
@ -117,6 +117,7 @@
|
||||
"copy-to-clipboard": "In Zwischenablage kopieren",
|
||||
"creation": "Schöpfen",
|
||||
"decay": {
|
||||
"Starting_block_decay": "Startblock Vergänglichkeit",
|
||||
"before_startblock_transaction": "Diese Transaktion beinhaltet keine Vergänglichkeit.",
|
||||
"calculation_decay": "Berechnung der Vergänglichkeit",
|
||||
"calculation_total": "Berechnung der Gesamtsumme",
|
||||
@ -127,7 +128,6 @@
|
||||
"new_balance": "Neuer Kontostand",
|
||||
"old_balance": "Vorheriger Kontostand",
|
||||
"past_time": "Vergangene Zeit",
|
||||
"Starting_block_decay": "Startblock Vergänglichkeit",
|
||||
"total": "Gesamt",
|
||||
"types": {
|
||||
"creation": "Geschöpft",
|
||||
@ -186,8 +186,8 @@
|
||||
"link_decay_description": "Der Link-Betrag wird zusammen mit der maximalen Vergänglichkeit blockiert. Nach Einlösen, Verfallen oder Löschen des Links wird der Rest wieder freigegeben.",
|
||||
"memo": "Nachricht",
|
||||
"message": "Nachricht",
|
||||
"new_balance": "Neuer Kontostand nach Bestätigung",
|
||||
"newPasswordRepeat": "Neues Passwort wiederholen",
|
||||
"new_balance": "Neuer Kontostand nach Bestätigung",
|
||||
"no_gdd_available": "Du hast keine GDD zum versenden.",
|
||||
"ok": "Ok",
|
||||
"password": "Passwort",
|
||||
@ -201,11 +201,11 @@
|
||||
"reset": "Zurücksetzen",
|
||||
"save": "Speichern",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scanne den QR Code deines Partners",
|
||||
"sender": "Absender",
|
||||
"send_check": "Bestätige deine Transaktion. Prüfe bitte nochmal alle Angaben!",
|
||||
"send_now": "Jetzt senden",
|
||||
"send_transaction_error": "Leider konnte die Transaktion nicht ausgeführt werden!",
|
||||
"send_transaction_success": "Deine Transaktion wurde erfolgreich ausgeführt",
|
||||
"sender": "Absender",
|
||||
"sorry": "Entschuldigung",
|
||||
"thx": "Danke",
|
||||
"to": "bis",
|
||||
@ -214,28 +214,28 @@
|
||||
"username-placeholder": "Wähle deinen Benutzernamen",
|
||||
"validation": {
|
||||
"amount": {
|
||||
"min": "Der Betrag sollte mindestens {min} groß sein.",
|
||||
"max": "Der Betrag sollte höchstens {max} groß sein.",
|
||||
"decimal-places": "Der Betrag sollte maximal zwei Nachkommastellen enthalten.",
|
||||
"max": "Der Betrag sollte höchstens {max} groß sein.",
|
||||
"min": "Der Betrag sollte mindestens {min} groß sein.",
|
||||
"typeError": "Der Betrag sollte eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein."
|
||||
},
|
||||
"contributionDate": {
|
||||
"required": "Das Beitragsdatum ist ein Pflichtfeld.",
|
||||
"max": "Das Späteste Beitragsdatum ist heute, der {max}.",
|
||||
"min": "Das Frühste Beitragsdatum ist {min}.",
|
||||
"max": "Das Späteste Beitragsdatum ist heute, der {max}."
|
||||
"required": "Das Beitragsdatum ist ein Pflichtfeld."
|
||||
},
|
||||
"contributionMemo": {
|
||||
"min": "Die Tätigkeitsbeschreibung sollte mindestens {min} Zeichen lang sein.",
|
||||
"max": "Die Tätigkeitsbeschreibung sollte höchstens {max} Zeichen lang sein.",
|
||||
"min": "Die Tätigkeitsbeschreibung sollte mindestens {min} Zeichen lang sein.",
|
||||
"required": "Die Tätigkeitsbeschreibung ist ein Pflichtfeld."
|
||||
},
|
||||
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein.",
|
||||
"hours": {
|
||||
"min": "Die Stunden sollten mindestens {min} groß sein.",
|
||||
"max": "Die Stunden sollten höchstens {max} groß sein.",
|
||||
"decimal-places": "Die Stunden sollten maximal zwei Nachkommastellen enthalten.",
|
||||
"max": "Die Stunden sollten höchstens {max} groß sein.",
|
||||
"min": "Die Stunden sollten mindestens {min} groß sein.",
|
||||
"typeError": "Die Stunden sollten eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein."
|
||||
},
|
||||
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein.",
|
||||
"identifier": {
|
||||
"communityIsReachable": "Community nicht gefunden oder nicht erreichbar!",
|
||||
"required": "Der Empfänger ist ein Pflichtfeld.",
|
||||
@ -243,8 +243,8 @@
|
||||
},
|
||||
"is-not": "Du kannst dir selbst keine Gradidos überweisen!",
|
||||
"memo": {
|
||||
"min": "Die Nachricht sollte mindestens {min} Zeichen lang sein.",
|
||||
"max": "Die Nachricht sollte höchstens {max} Zeichen lang sein.",
|
||||
"min": "Die Nachricht sollte mindestens {min} Zeichen lang sein.",
|
||||
"required": "Die Nachricht ist ein Pflichtfeld."
|
||||
},
|
||||
"requiredField": "{fieldName} ist ein Pflichtfeld",
|
||||
@ -347,13 +347,13 @@
|
||||
},
|
||||
"openHours": "Offene Stunden",
|
||||
"pageTitle": {
|
||||
"circles": "Gradido Kreise",
|
||||
"contributions": "Gradido schöpfen",
|
||||
"gdt": "Deine GDT Transaktionen",
|
||||
"information": "{community}",
|
||||
"overview": "Willkommen {name}",
|
||||
"send": "Sende Gradidos",
|
||||
"settings": "Einstellungen",
|
||||
"circles": "Gradido Kreise",
|
||||
"transactions": "Deine Transaktionen",
|
||||
"usersearch": "Geografische Mitgliedssuche (beta)"
|
||||
},
|
||||
@ -367,8 +367,6 @@
|
||||
"warningText": "Bist du noch da?"
|
||||
},
|
||||
"settings": {
|
||||
"community": "Kreise & Mitgliedssuche",
|
||||
"emailInfo": "Kann aktuell noch nicht geändert werden.",
|
||||
"GMS": {
|
||||
"disabled": "Daten werden nicht nach GMS exportiert",
|
||||
"enabled": "Daten werden nach GMS exportiert",
|
||||
@ -383,20 +381,22 @@
|
||||
"communityCoords": "Ihr Gemeinschafts-Standort: Breitengrad {lat}, Längengrad {lng}",
|
||||
"communityLocationLabel": "Ihr Gemeinschafts-Standort",
|
||||
"headline": "Geografische Standorterfassung des Benutzers",
|
||||
"search": "Nach einem Standort suchen",
|
||||
"userCoords": "Ihr Standort: Breitengrad {lat}, Längengrad {lng}",
|
||||
"userLocationLabel": "Ihr Standort",
|
||||
"search": "Nach einem Standort suchen"
|
||||
"userLocationLabel": "Ihr Standort"
|
||||
},
|
||||
"naming-format": "Namen anzeigen:",
|
||||
"publish-location": {
|
||||
"exact": "Genaue Position",
|
||||
"approximate": "Ungefähre Position",
|
||||
"exact": "Genaue Position",
|
||||
"updated": "Positionstyp für GMS aktualisiert"
|
||||
},
|
||||
"publish-name": {
|
||||
"updated": "Namensformat für GMS aktualisiert"
|
||||
}
|
||||
},
|
||||
"community": "Kreise & Mitgliedssuche",
|
||||
"emailInfo": "Kann aktuell noch nicht geändert werden.",
|
||||
"hideAmountGDD": "Dein GDD Betrag ist versteckt.",
|
||||
"hideAmountGDT": "Dein GDT Betrag ist versteckt.",
|
||||
"humhub": {
|
||||
@ -448,9 +448,9 @@
|
||||
"alias-or-initials": "Benutzername oder Initialen",
|
||||
"alias-or-initials-tooltip": "Benutzername, falls vorhanden, oder die Initialen von Vorname und Nachname jeweils die ersten zwei Buchstaben",
|
||||
"first": "Vorname",
|
||||
"first-tooltip": "Nur der Vornamen",
|
||||
"first-initial": "Vorname und Initial",
|
||||
"first-initial-tooltip": "Vornamen plus den ersten Anfangsbuchstaben des Nachnamens",
|
||||
"first-tooltip": "Nur der Vornamen",
|
||||
"initials": "Initialen",
|
||||
"initials-tooltip": "Initialen von Vor- und Nachname also jeweils die ersten zwei Buchstaben unabhängig von der Existenz des Benutzernamens",
|
||||
"name-full": "Vorname und Nachname",
|
||||
@ -507,9 +507,9 @@
|
||||
"send_you": "sendet dir"
|
||||
},
|
||||
"usersearch": {
|
||||
"button": "Starte die Nutzersuche...",
|
||||
"headline": "Geografische Nutzersuche",
|
||||
"text": "Ganz gleich zu welcher Community du gehörst, mit dem Geo Matching System findest du Mitglieder aller Communities auf einer Landkarte. Du kannst nach Angeboten und Bedürfnissen filtern und dir die Nutzer anzeigen lassen, die zu Dir passen.\n\nMit dem Button wird ein neues Browser-Fenster geöffnet, in dem dir die Nutzer in deinem Umfeld auf einer Karte angezeigt werden.",
|
||||
"button": "Starte die Nutzersuche..."
|
||||
"text": "Ganz gleich zu welcher Community du gehörst, mit dem Geo Matching System findest du Mitglieder aller Communities auf einer Landkarte. Du kannst nach Angeboten und Bedürfnissen filtern und dir die Nutzer anzeigen lassen, die zu Dir passen.\n\nMit dem Button wird ein neues Browser-Fenster geöffnet, in dem dir die Nutzer in deinem Umfeld auf einer Karte angezeigt werden."
|
||||
},
|
||||
"via_link": "über einen Link",
|
||||
"welcome": "Willkommen in der Gemeinschaft"
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
{
|
||||
"85": "85%",
|
||||
"100": "100%",
|
||||
"125": "125%",
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 thanks for being with us!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"Chat": "Chat",
|
||||
"GDD": "GDD",
|
||||
"GDD-long": "Gradido",
|
||||
"GDT": "GDT",
|
||||
"GMS": {
|
||||
"title": "Geo Matching System GMS (in develop)",
|
||||
"desc": "Find members of all communities on a map."
|
||||
"desc": "Find members of all communities on a map.",
|
||||
"title": "Geo Matching System GMS (in develop)"
|
||||
},
|
||||
"Humhub": {
|
||||
"title": "Gradido-circles",
|
||||
"desc": "Together we support each other - mindful in circle culture."
|
||||
"desc": "Together we support each other - mindful in circle culture.",
|
||||
"title": "Gradido-circles"
|
||||
},
|
||||
"PersonalDetails": "Personal details",
|
||||
"advanced-calculation": "Advanced calculation",
|
||||
@ -34,27 +34,27 @@
|
||||
},
|
||||
"back": "Back",
|
||||
"card-circles": {
|
||||
"headline": "Cooperation platform “Gradido Circles”",
|
||||
"text": "Local circles, study circles, projects, events and congresses",
|
||||
"allowed": {
|
||||
"button": "Open Circles..."
|
||||
},
|
||||
"headline": "Cooperation platform “Gradido Circles”",
|
||||
"not-allowed": {
|
||||
"button": "Configurate..."
|
||||
}
|
||||
},
|
||||
"text": "Local circles, study circles, projects, events and congresses"
|
||||
},
|
||||
"card-user-search": {
|
||||
"headline": "Geographic member search (beta)",
|
||||
"allowed": {
|
||||
"button": "Open member search...",
|
||||
"disabled-button": "GMS offline...",
|
||||
"text": "Find Members of all Communities on a Map."
|
||||
},
|
||||
"headline": "Geographic member search (beta)",
|
||||
"info": "How it works",
|
||||
"not-allowed": {
|
||||
"button": "Enter location...",
|
||||
"text": "To find other members in your area, enter your location on the map now!"
|
||||
},
|
||||
"info": "How it works"
|
||||
}
|
||||
},
|
||||
"community": {
|
||||
"admins": "Administrators",
|
||||
@ -95,8 +95,8 @@
|
||||
"lastContribution": "Last Contributions",
|
||||
"noContributions": {
|
||||
"allContributions": "No contributions have been submitted yet.",
|
||||
"myContributions": "You have not submitted any entries yet.",
|
||||
"emptyPage": "This page is empty."
|
||||
"emptyPage": "This page is empty.",
|
||||
"myContributions": "You have not submitted any entries yet."
|
||||
},
|
||||
"noOpenCreation": {
|
||||
"allMonth": "For all two months your creation limit is reached. The next month you can create 1000 GDD again.",
|
||||
@ -117,6 +117,7 @@
|
||||
"copy-to-clipboard": "Copy to clipboard",
|
||||
"creation": "Creation",
|
||||
"decay": {
|
||||
"Starting_block_decay": "Starting Block Decay",
|
||||
"before_startblock_transaction": "This transaction does not include decay.",
|
||||
"calculation_decay": "Calculation of Decay",
|
||||
"calculation_total": "Calculation of the total Amount",
|
||||
@ -127,7 +128,6 @@
|
||||
"new_balance": "New balance",
|
||||
"old_balance": "Previous balance",
|
||||
"past_time": "Time passed",
|
||||
"Starting_block_decay": "Starting Block Decay",
|
||||
"total": "Total",
|
||||
"types": {
|
||||
"creation": "Created",
|
||||
@ -186,8 +186,8 @@
|
||||
"link_decay_description": "The link amount is blocked along with the maximum decay. After the link has been redeemed, expired, or deleted, the rest is released again.",
|
||||
"memo": "Message",
|
||||
"message": "Message",
|
||||
"new_balance": "Account balance after confirmation",
|
||||
"newPasswordRepeat": "Repeat new password",
|
||||
"new_balance": "Account balance after confirmation",
|
||||
"no_gdd_available": "You do not have GDD to send.",
|
||||
"ok": "Ok",
|
||||
"password": "Password",
|
||||
@ -201,11 +201,11 @@
|
||||
"reset": "Reset",
|
||||
"save": "Save",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scan the QR Code of your partner",
|
||||
"sender": "Sender",
|
||||
"send_check": "Confirm your transaction. Please check all data again!",
|
||||
"send_now": "Send now",
|
||||
"send_transaction_error": "Unfortunately, the transaction could not be executed!",
|
||||
"send_transaction_success": "Your transaction was successfully completed",
|
||||
"sender": "Sender",
|
||||
"sorry": "Sorry",
|
||||
"thx": "Thank you",
|
||||
"to": "to",
|
||||
@ -214,28 +214,28 @@
|
||||
"username-placeholder": "Choose your username",
|
||||
"validation": {
|
||||
"amount": {
|
||||
"min": "The amount should be at least {min} in size.",
|
||||
"max": "The amount should not be larger than {max}.",
|
||||
"decimal-places": "The amount should contain a maximum of two decimal places.",
|
||||
"max": "The amount should not be larger than {max}.",
|
||||
"min": "The amount should be at least {min} in size.",
|
||||
"typeError": "The amount should be a number between {min} and {max} with at most two digits after the decimal point."
|
||||
},
|
||||
"contributionDate": {
|
||||
"required": "The contribution date is a required field.",
|
||||
"max": "The latest contribution date is today, {max}.",
|
||||
"min": "The earliest contribution date is {min}.",
|
||||
"max": "The latest contribution date is today, {max}."
|
||||
"required": "The contribution date is a required field."
|
||||
},
|
||||
"contributionMemo": {
|
||||
"min": "The job description should be at least {min} characters long.",
|
||||
"max": "The job description should not be longer than {max} characters.",
|
||||
"min": "The job description should be at least {min} characters long.",
|
||||
"required": "The job description is required."
|
||||
},
|
||||
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits after the decimal point.",
|
||||
"hours": {
|
||||
"min": "The hours should be at least {min} in size.",
|
||||
"max": "The hours should not be larger than {max}.",
|
||||
"decimal-places": "The hours should contain a maximum of two decimal places.",
|
||||
"max": "The hours should not be larger than {max}.",
|
||||
"min": "The hours should be at least {min} in size.",
|
||||
"typeError": "The hours should be a number between {min} and {max} with at most two digits after the decimal point."
|
||||
},
|
||||
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits after the decimal point.",
|
||||
"identifier": {
|
||||
"communityIsReachable": "Community not found our not reachable!",
|
||||
"required": "The recipient is a required field.",
|
||||
@ -243,8 +243,8 @@
|
||||
},
|
||||
"is-not": "You cannot send Gradidos to yourself!",
|
||||
"memo": {
|
||||
"min": "The message should be at least {min} characters long.",
|
||||
"max": "The message should not be longer than {max} characters.",
|
||||
"min": "The message should be at least {min} characters long.",
|
||||
"required": "The message is required."
|
||||
},
|
||||
"requiredField": "The {fieldName} field is required",
|
||||
@ -347,6 +347,7 @@
|
||||
},
|
||||
"openHours": "Open Hours",
|
||||
"pageTitle": {
|
||||
"circles": "Gradido Circles",
|
||||
"contributions": "Create Gradido",
|
||||
"gdt": "Your GDT transactions",
|
||||
"information": "{community}",
|
||||
@ -354,7 +355,6 @@
|
||||
"send": "Send Gradidos",
|
||||
"settings": "Settings",
|
||||
"transactions": "Your transactions",
|
||||
"circles": "Gradido Circles",
|
||||
"usersearch": "Geographic member search (beta)"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
@ -367,8 +367,6 @@
|
||||
"warningText": "Are you still there?"
|
||||
},
|
||||
"settings": {
|
||||
"community": "Community",
|
||||
"emailInfo": "Cannot be changed at this time.",
|
||||
"GMS": {
|
||||
"disabled": "Data not exported to GMS",
|
||||
"enabled": "Data exported to GMS",
|
||||
@ -383,20 +381,22 @@
|
||||
"communityCoords": "Your Community Location: Lat {lat}, Lng {lng}",
|
||||
"communityLocationLabel": "Your Community-Location",
|
||||
"headline": "Geographic Location-Capturing of the User",
|
||||
"search": "Search for a location",
|
||||
"userCoords": "Your Location: Lat {lat}, Lng {lng}",
|
||||
"userLocationLabel": "Your Location",
|
||||
"search": "Search for a location"
|
||||
"userLocationLabel": "Your Location"
|
||||
},
|
||||
"naming-format": "Show Name:",
|
||||
"publish-location": {
|
||||
"exact": "exact position",
|
||||
"approximate": "approximate position",
|
||||
"exact": "exact position",
|
||||
"updated": "format of location for GMS updated"
|
||||
},
|
||||
"publish-name": {
|
||||
"updated": "format of name for GMS updated"
|
||||
}
|
||||
},
|
||||
"community": "Community",
|
||||
"emailInfo": "Cannot be changed at this time.",
|
||||
"hideAmountGDD": "Your GDD amount is hidden.",
|
||||
"hideAmountGDT": "Your GDT amount is hidden.",
|
||||
"humhub": {
|
||||
@ -448,9 +448,9 @@
|
||||
"alias-or-initials": "Username or initials (Default)",
|
||||
"alias-or-initials-tooltip": "username, if available, or the initials of the first name and last name, the first two letters in each case",
|
||||
"first": "Firstname",
|
||||
"first-tooltip": "the first name only",
|
||||
"first-initial": "First name and initial",
|
||||
"first-initial-tooltip": "first name plus the first letter of the last name",
|
||||
"first-tooltip": "the first name only",
|
||||
"initials": "Initials",
|
||||
"initials-tooltip": "Initials of first name and last name, i.e. the first two letters of each regardless of the existence of the user name",
|
||||
"name-full": "first name and last name",
|
||||
@ -507,9 +507,9 @@
|
||||
"send_you": "wants to send you"
|
||||
},
|
||||
"usersearch": {
|
||||
"button": "Start user search... ",
|
||||
"headline": "Geographical User Search",
|
||||
"text": "No matter which community you belong to, with the Geo Matching System you can find members of all communities on a map. You can filter according to offers and needs and display the users that match you.\n\nThe button opens a new browser window in which the users in your area are displayed on a map.",
|
||||
"button": "Start user search... "
|
||||
"text": "No matter which community you belong to, with the Geo Matching System you can find members of all communities on a map. You can filter according to offers and needs and display the users that match you.\n\nThe button opens a new browser window in which the users in your area are displayed on a map."
|
||||
},
|
||||
"via_link": "via Link",
|
||||
"welcome": "Welcome to the community"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 Gracias, por estar con nosotros!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"100": "100%",
|
||||
"125": "125%",
|
||||
"1000thanks": "1000 Gracias, por estar con nosotros!",
|
||||
"GDD": "GDD",
|
||||
"GDT": "GDT",
|
||||
"PersonalDetails": "Datos personales",
|
||||
@ -22,32 +22,32 @@
|
||||
},
|
||||
"back": "Volver",
|
||||
"card-circles": {
|
||||
"headline": "Plataforma de cooperación «Círculos Gradido»",
|
||||
"text": "Círculos locales, círculos de estudio, proyectos, ev entos y congresos",
|
||||
"allowed": {
|
||||
"button": "Abrir círculos..."
|
||||
},
|
||||
"headline": "Plataforma de cooperación «Círculos Gradido»",
|
||||
"not-allowed": {
|
||||
"button": "Configurar..."
|
||||
}
|
||||
},
|
||||
"text": "Círculos locales, círculos de estudio, proyectos, ev entos y congresos"
|
||||
},
|
||||
"card-user-search": {
|
||||
"headline": "Búsqueda geográfica de miembros (beta)",
|
||||
"allowed": {
|
||||
"button": "Abrir búsqueda de miembros...",
|
||||
"disabled-button": "GMS offline...",
|
||||
"text": "Encuentra Miembros de todas las Comunidades en un Mapa."
|
||||
},
|
||||
"headline": "Búsqueda geográfica de miembros (beta)",
|
||||
"info": "Así se hace",
|
||||
"not-allowed": {
|
||||
"button": "Introducir ubicación...",
|
||||
"text": "Para encontrar otros miembros cerca de ti, ¡introduce ahora tu ubicación en el mapa!"
|
||||
},
|
||||
"info": "Así se hace"
|
||||
}
|
||||
},
|
||||
"circles": {
|
||||
"button": "Abrir Círculos...",
|
||||
"headline": "Juntos nos apoyamos - atentos a la cultura de los círculos.",
|
||||
"text": "Haga clic en el botón para abrir la plataforma de cooperación en una nueva ventana del navegador.",
|
||||
"button": "Abrir Círculos..."
|
||||
"text": "Haga clic en el botón para abrir la plataforma de cooperación en una nueva ventana del navegador."
|
||||
},
|
||||
"community": {
|
||||
"admins": "Administradores",
|
||||
@ -60,8 +60,8 @@
|
||||
"moderators": "Moderadores",
|
||||
"myContributions": "Mis contribuciones al bien común",
|
||||
"noOpenContributionLinkText": "Actualmente no hay creaciones generadas por enlaces.",
|
||||
"openContributionLinks": "Creaciones generadas por enlace",
|
||||
"openContributionLinkText": "Para créditos iniciales o fines similares, la comunidad puede crear los llamados enlaces de creación. Éstos activan creaciones automáticas que se acreditan al usuario.\nLa comunidad \"{name}\" admite actualmente {count} creaciones generadas por enlaces:",
|
||||
"openContributionLinks": "Creaciones generadas por enlace",
|
||||
"other-communities": "Otras comunidades",
|
||||
"startNewsButton": "Enviar Gradidos",
|
||||
"statistic": "Estadísticas",
|
||||
@ -103,6 +103,7 @@
|
||||
},
|
||||
"copy-to-clipboard": "Copiar al portapapeles",
|
||||
"decay": {
|
||||
"Starting_block_decay": "Startblock disminución gradual",
|
||||
"before_startblock_transaction": "Esta transacción no implica disminución en su valor.",
|
||||
"calculation_decay": "Cálculo de la disminución gradual del valor",
|
||||
"calculation_total": "Cálculo de la suma total",
|
||||
@ -111,7 +112,6 @@
|
||||
"decay_since_last_transaction": "Disminución gradual",
|
||||
"last_transaction": "Transacción anterior",
|
||||
"past_time": "Tiempo transcurrido",
|
||||
"Starting_block_decay": "Startblock disminución gradual",
|
||||
"total": "Total",
|
||||
"types": {
|
||||
"creation": "Creado",
|
||||
@ -166,8 +166,8 @@
|
||||
"link_decay_description": "El importe del enlace se bloquea junto con la ransience máxima. Una vez que el enlace se ha canjeado, caducado o eliminado, el resto se libera de nuevo.",
|
||||
"memo": "Mensaje",
|
||||
"message": "Noticia",
|
||||
"new_balance": "Saldo de cuenta nuevo depués de confirmación",
|
||||
"newPasswordRepeat": "Repetir contraseña nueva",
|
||||
"new_balance": "Saldo de cuenta nuevo depués de confirmación",
|
||||
"no_gdd_available": "No dispones de GDD para enviar.",
|
||||
"password": "Contraseña",
|
||||
"passwordRepeat": "Repetir contraseña",
|
||||
@ -179,11 +179,11 @@
|
||||
"reset": "Restablecer",
|
||||
"save": "Guardar",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Escanea el código QR de tu pareja",
|
||||
"sender": "Remitente",
|
||||
"send_check": "Confirma tu transacción. Por favor revisa toda la información nuevamente!",
|
||||
"send_now": "Enviar ahora",
|
||||
"send_transaction_error": "Desafortunadamente, la transacción no se pudo ejecutar!",
|
||||
"send_transaction_success": "Su transacción fue ejecutada con éxito",
|
||||
"sender": "Remitente",
|
||||
"sorry": "Disculpa",
|
||||
"thx": "Gracias",
|
||||
"time": "Tiempo",
|
||||
@ -295,6 +295,7 @@
|
||||
},
|
||||
"openHours": "Open Hours",
|
||||
"pageTitle": {
|
||||
"circles": "Círculos Gradido",
|
||||
"contributions": "Cuchara Gradido",
|
||||
"gdt": "Tu GDT Transacciones",
|
||||
"information": "{community}",
|
||||
@ -302,7 +303,6 @@
|
||||
"send": "Enviar Gradidos",
|
||||
"settings": "Soporte",
|
||||
"transactions": "Tu Transacciones",
|
||||
"circles": "Círculos Gradido",
|
||||
"usersearch": "Búsqueda geográfica de miembros (beta)"
|
||||
},
|
||||
"qrCode": "Código QR",
|
||||
@ -326,10 +326,10 @@
|
||||
"communityCoords": "Ubicación de tu comunidad: Lat {lat}, Lng {lng}",
|
||||
"communityLocationLabel": "Ubicación de tu comunidad",
|
||||
"headline": "Captura de ubicación geográfica del usuario",
|
||||
"userCoords": "Tu ubicación: Lat {lat}, Lng {lng}",
|
||||
"userLocationLabel": "Tu ubicación",
|
||||
"search": "Buscar una ubicación",
|
||||
"success": "Ubicación guardada exitosamente"
|
||||
"success": "Ubicación guardada exitosamente",
|
||||
"userCoords": "Tu ubicación: Lat {lat}, Lng {lng}",
|
||||
"userLocationLabel": "Tu ubicación"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
@ -414,9 +414,9 @@
|
||||
"send_you": "te envía"
|
||||
},
|
||||
"usersearch": {
|
||||
"button": "Iniciar la búsqueda de usuarios...",
|
||||
"headline": "Búsqueda geográfica de usuarios",
|
||||
"text": "No importa a qué comunidad pertenezcas, con el Geo Matching System puedes encontrar miembros de todas las comunidades en un mapa. Puedes filtrar según ofertas y requisitos y visualizar los usuarios que coinciden con tu perfil.\n\nEl botón abre una nueva ventana del navegador en la que se muestran en un mapa los usuarios de tu zona.",
|
||||
"button": "Iniciar la búsqueda de usuarios..."
|
||||
"text": "No importa a qué comunidad pertenezcas, con el Geo Matching System puedes encontrar miembros de todas las comunidades en un mapa. Puedes filtrar según ofertas y requisitos y visualizar los usuarios que coinciden con tu perfil.\n\nEl botón abre una nueva ventana del navegador en la que se muestran en un mapa los usuarios de tu zona."
|
||||
},
|
||||
"via_link": "atraves de un enlace",
|
||||
"welcome": "Bienvenido a la comunidad."
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"85": "85%",
|
||||
"100": "100%",
|
||||
"125": "125%",
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 mercis d'être avec nous!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"GDD": "GDD",
|
||||
"GDT": "GDT",
|
||||
"PersonalDetails": "Informations personnelles",
|
||||
@ -24,32 +24,32 @@
|
||||
},
|
||||
"back": "Retour",
|
||||
"card-circles": {
|
||||
"headline": "Plate-forme de coopération «Cercles Gradido»",
|
||||
"text": "Cercles locaux, cercles d'études, projets, événements et congrès",
|
||||
"allowed": {
|
||||
"button": "Ouvrir les cercles..."
|
||||
},
|
||||
"headline": "Plate-forme de coopération «Cercles Gradido»",
|
||||
"not-allowed": {
|
||||
"button": "Configurer..."
|
||||
}
|
||||
},
|
||||
"text": "Cercles locaux, cercles d'études, projets, événements et congrès"
|
||||
},
|
||||
"card-user-search": {
|
||||
"headline": "Recherche géographique de membres (bêta)",
|
||||
"allowed": {
|
||||
"button": "Ouvrir la recherche de membres...",
|
||||
"disabled-button": "GMS offline...",
|
||||
"text": "Trouve des membres de toutes les communautés sur une carte."
|
||||
},
|
||||
"headline": "Recherche géographique de membres (bêta)",
|
||||
"info": "Comment ça marche",
|
||||
"not-allowed": {
|
||||
"button": "Indiquer ta position...",
|
||||
"text": "Pour trouver d'autres membres près de chez toi, indique dès maintenant ta position sur la carte!"
|
||||
},
|
||||
"info": "Comment ça marche"
|
||||
}
|
||||
},
|
||||
"circles": {
|
||||
"button": "Ouvrir les Cercles...",
|
||||
"headline": "Ensemble, nous nous soutenons mutuellement - attentifs à la culture du cercle.",
|
||||
"text": "En cliquant sur le bouton, tu ouvres la plateforme de coopération dans une nouvelle fenêtre de navigation.",
|
||||
"button": "Ouvrir les Cercles..."
|
||||
"text": "En cliquant sur le bouton, tu ouvres la plateforme de coopération dans une nouvelle fenêtre de navigation."
|
||||
},
|
||||
"community": {
|
||||
"admins": "Administrateurs",
|
||||
@ -61,8 +61,8 @@
|
||||
"moderators": "Modérateurs",
|
||||
"myContributions": "Mes contributions",
|
||||
"noOpenContributionLinkText": "Actuellement, il n'y a pas de créations générées par lien.",
|
||||
"openContributionLinks": "Créations générées par lien",
|
||||
"openContributionLinkText": "Pour les crédits de départ ou à des fins similaires, la communauté peut créer des \"liens de création\". Ils déclenchent des créations automatiques qui sont créditées à l'utilisateur.\nLa communauté \"{name}\" soutient actuellement {count} créations générées par lien:",
|
||||
"openContributionLinks": "Créations générées par lien",
|
||||
"startNewsButton": "Envoyer des gradidos",
|
||||
"submitContribution": "Contribuer"
|
||||
},
|
||||
@ -106,6 +106,7 @@
|
||||
"copy-to-clipboard": "Copier dans le presse-papier",
|
||||
"creation": "Création",
|
||||
"decay": {
|
||||
"Starting_block_decay": "Début de la décroissance",
|
||||
"before_startblock_transaction": "Cette transaction n'est pas péremptoire.",
|
||||
"calculation_decay": "Calcul de la décroissance",
|
||||
"calculation_total": "Calcul du montant total",
|
||||
@ -114,7 +115,6 @@
|
||||
"decay_since_last_transaction": "Décroissance depuis la dernière transaction",
|
||||
"last_transaction": "Dernière transaction:",
|
||||
"past_time": "Le temps a expiré",
|
||||
"Starting_block_decay": "Début de la décroissance",
|
||||
"total": "Total",
|
||||
"types": {
|
||||
"creation": "Créé",
|
||||
@ -172,8 +172,8 @@
|
||||
"link_decay_description": "Le montant du lien est bloqué avec le dépérissement maximale. Une fois le lien utilisé, expiré ou supprimé, le reste est à nouveau débloqué.",
|
||||
"memo": "Note",
|
||||
"message": "Message",
|
||||
"new_balance": "Montant du solde après confirmation",
|
||||
"newPasswordRepeat": "Répétez le nouveau mot de passe",
|
||||
"new_balance": "Montant du solde après confirmation",
|
||||
"no_gdd_available": "Vous n'avez pas de GDD à envoyer.",
|
||||
"password": "Mot de passe",
|
||||
"passwordRepeat": "Répétez le mot de passe",
|
||||
@ -185,11 +185,11 @@
|
||||
"reset": "Réinitialiser",
|
||||
"save": "Sauvegarder",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scannez le QR code de votre partenaire",
|
||||
"sender": "Expéditeur",
|
||||
"send_check": "Confirmez la transaction. Veuillez revérifier toutes les données svp!",
|
||||
"send_now": "Envoyez maintenant",
|
||||
"send_transaction_error": "Malheureusement, la transaction n'a pas pu être effectuée!",
|
||||
"send_transaction_success": "Votre transaction a été effectuée avec succès",
|
||||
"sender": "Expéditeur",
|
||||
"sorry": "Désolé",
|
||||
"thx": "Merci",
|
||||
"to": "à",
|
||||
@ -303,6 +303,7 @@
|
||||
},
|
||||
"openHours": "Heures ouverte",
|
||||
"pageTitle": {
|
||||
"circles": "Cercles Gradido",
|
||||
"contributions": "Ma communauté",
|
||||
"gdt": "Vos transactions GDT",
|
||||
"information": "{community}",
|
||||
@ -310,7 +311,6 @@
|
||||
"send": "Envoyé Gradidos",
|
||||
"settings": "Configuration",
|
||||
"transactions": "Vos transactions",
|
||||
"circles": "Cercles Gradido",
|
||||
"usersearch": "Recherche géographique de membres (bèta)"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
@ -334,10 +334,10 @@
|
||||
"communityCoords": "Emplacement de votre communauté : Lat {lat}, Long {lng}",
|
||||
"communityLocationLabel": "Emplacement de votre communauté",
|
||||
"headline": "Capture de la localisation géographique de l'utilisateur",
|
||||
"userCoords": "Votre emplacement : Lat {lat}, Long {lng}",
|
||||
"userLocationLabel": "Votre emplacement",
|
||||
"search": "Rechercher un emplacement",
|
||||
"success": "Emplacement enregistré avec succès"
|
||||
"success": "Emplacement enregistré avec succès",
|
||||
"userCoords": "Votre emplacement : Lat {lat}, Long {lng}",
|
||||
"userLocationLabel": "Votre emplacement"
|
||||
}
|
||||
},
|
||||
"hideAmountGDD": "Votre montant GDD est caché.",
|
||||
@ -422,9 +422,9 @@
|
||||
"send_you": "veut vous envoyer"
|
||||
},
|
||||
"usersearch": {
|
||||
"button": "Commence la recherche d'utilisateurs...",
|
||||
"headline": "Recherche géographique d'utilisateurs",
|
||||
"text": "Quelle que soit la communauté à laquelle tu appartiens, le système de géo-matching te permet de trouver des membres de toutes les communautés sur une carte géographique. Tu peux filtrer selon les offres et les besoins et afficher les utilisateurs qui te correspondent.\n\nEn cliquant sur le bouton, une nouvelle fenêtre de navigateur s'ouvre et t'affiche les utilisateurs de ton entourage sur une carte.",
|
||||
"button": "Commence la recherche d'utilisateurs..."
|
||||
"text": "Quelle que soit la communauté à laquelle tu appartiens, le système de géo-matching te permet de trouver des membres de toutes les communautés sur une carte géographique. Tu peux filtrer selon les offres et les besoins et afficher les utilisateurs qui te correspondent.\n\nEn cliquant sur le bouton, une nouvelle fenêtre de navigateur s'ouvre et t'affiche les utilisateurs de ton entourage sur une carte."
|
||||
},
|
||||
"via_link": "par lien",
|
||||
"welcome": "Bienvenu dans la communauté"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"100": "100%",
|
||||
"1000thanks": "1000 dank, omdat je bij ons bent!",
|
||||
"125": "125%",
|
||||
"85": "85%",
|
||||
"100": "100%",
|
||||
"125": "125%",
|
||||
"1000thanks": "1000 dank, omdat je bij ons bent!",
|
||||
"GDD": "GDD",
|
||||
"GDT": "GDT",
|
||||
"PersonalDetails": "Persoonlijke gegevens",
|
||||
@ -22,32 +22,32 @@
|
||||
},
|
||||
"back": "Terug",
|
||||
"card-circles": {
|
||||
"headline": "Samenwerkingsplatform “Gradido Kringen”",
|
||||
"text": "Lokale kringen, studiekringen, projecten, evenementen en congressen",
|
||||
"allowed": {
|
||||
"button": "Kringen openen..."
|
||||
},
|
||||
"headline": "Samenwerkingsplatform “Gradido Kringen”",
|
||||
"not-allowed": {
|
||||
"button": "Configureren..."
|
||||
}
|
||||
},
|
||||
"text": "Lokale kringen, studiekringen, projecten, evenementen en congressen"
|
||||
},
|
||||
"card-user-search": {
|
||||
"headline": "Geografisch leden zoeken (bèta)",
|
||||
"allowed": {
|
||||
"button": "Leden zoeken openen...",
|
||||
"disabled-button": "GMS offline...",
|
||||
"text": "Vind leden van alle gemeenschappen op een kaart."
|
||||
},
|
||||
"headline": "Geografisch leden zoeken (bèta)",
|
||||
"info": "Zo gaat dat",
|
||||
"not-allowed": {
|
||||
"button": "Locatie invoeren",
|
||||
"text": "Om andere leden in jouw omgeving te vinden, voer nu je locatie in op de kaart!"
|
||||
},
|
||||
"info": "Zo gaat dat"
|
||||
}
|
||||
},
|
||||
"circles": {
|
||||
"button": "Cirkels openen...",
|
||||
"headline": "Samen ondersteunen we elkaar - mindful in de cirkelcultuur.",
|
||||
"text": "Klik op de knop om het samenwerkingsplatform te openen in een nieuw browservenster.",
|
||||
"button": "Cirkels openen..."
|
||||
"text": "Klik op de knop om het samenwerkingsplatform te openen in een nieuw browservenster."
|
||||
},
|
||||
"community": {
|
||||
"admins": "Beheerders",
|
||||
@ -60,8 +60,8 @@
|
||||
"moderators": "Moderators",
|
||||
"myContributions": "Mijn bijdragen voor het algemeen belang",
|
||||
"noOpenContributionLinkText": "Er zijn momenteel geen link-gegenereerde creaties.",
|
||||
"openContributionLinks": "Creaties gegenereerd door link",
|
||||
"openContributionLinkText": "Voor startcredits of soortgelijke doeleinden kan de community zogenaamde creatielinks maken. Deze activeren automatische creaties die worden gecrediteerd aan de gebruiker.\nDe community \"{name}\" ondersteunt momenteel {count} link-gegenereerde creaties:",
|
||||
"openContributionLinks": "Creaties gegenereerd door link",
|
||||
"other-communities": "Verdere gemeenschappen",
|
||||
"startNewsButton": "Stuur Gradidos",
|
||||
"statistic": "Statistieken",
|
||||
@ -103,6 +103,7 @@
|
||||
},
|
||||
"copy-to-clipboard": "Kopieer naar klembord",
|
||||
"decay": {
|
||||
"Starting_block_decay": "Startblok vergankelijkheid",
|
||||
"before_startblock_transaction": "Deze transactie heeft geen vergankelijkheid.",
|
||||
"calculation_decay": "Berekening van de vergankelijkheid",
|
||||
"calculation_total": "Berekening van het totaalbedrag",
|
||||
@ -111,7 +112,6 @@
|
||||
"decay_since_last_transaction": "Vergankelijkheid sinds de laatste transactie",
|
||||
"last_transaction": "Laatste transactie",
|
||||
"past_time": "Verlopen tijd",
|
||||
"Starting_block_decay": "Startblok vergankelijkheid",
|
||||
"total": "Totaal",
|
||||
"types": {
|
||||
"creation": "Gecreëerd",
|
||||
@ -166,8 +166,8 @@
|
||||
"link_decay_description": "Het linkbedrag wordt geblokkeerd samen met de maximale vergankelijkheid. Nadat de link is ingewisseld, verlopen of verwijderd, wordt het resterende bedrag weer vrijgegeven.",
|
||||
"memo": "Memo",
|
||||
"message": "Bericht",
|
||||
"new_balance": "Nieuw banksaldo na bevestiging",
|
||||
"newPasswordRepeat": "Nieuw wachtwoord herhalen",
|
||||
"new_balance": "Nieuw banksaldo na bevestiging",
|
||||
"no_gdd_available": "Je hebt geen GDD om te versturen.",
|
||||
"password": "Wachtwoord",
|
||||
"passwordRepeat": "Wachtwoord herhalen",
|
||||
@ -179,11 +179,11 @@
|
||||
"reset": "Resetten",
|
||||
"save": "Opslaan",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Scan de QR Code van uw partner",
|
||||
"sender": "Afzender",
|
||||
"send_check": "Bevestig jouw transactie. Controleer alsjeblieft nogmaals alle gegevens!",
|
||||
"send_now": "Nu versturen",
|
||||
"send_transaction_error": "Helaas kon de transactie niet uitgevoerd worden!",
|
||||
"send_transaction_success": " Jouw transactie werd succesvol uitgevoerd ",
|
||||
"sender": "Afzender",
|
||||
"sorry": "Sorry",
|
||||
"thx": "Dankjewel",
|
||||
"time": "Tijd",
|
||||
@ -295,6 +295,7 @@
|
||||
},
|
||||
"openHours": "Open Hours",
|
||||
"pageTitle": {
|
||||
"circles": "Gradido Kringen",
|
||||
"contributions": "Gradido scoop",
|
||||
"gdt": "Your GDT transactions",
|
||||
"information": "{community}",
|
||||
@ -302,7 +303,6 @@
|
||||
"send": "Send Gradidos",
|
||||
"settings": "Settings",
|
||||
"transactions": "Your transactions",
|
||||
"circles": "Gradido Kringen",
|
||||
"usersearch": "Geografisch leden zoeken (bèta)"
|
||||
},
|
||||
"qrCode": "QR Code",
|
||||
@ -326,10 +326,10 @@
|
||||
"communityCoords": "Locatie van je gemeenschap: Lat {lat}, Lng {lng}",
|
||||
"communityLocationLabel": "Locatie van je gemeenschap",
|
||||
"headline": "Geografische locatiebepaling van de gebruiker",
|
||||
"userCoords": "Jouw locatie: Lat {lat}, Lng {lng}",
|
||||
"userLocationLabel": "Jouw locatie",
|
||||
"search": "Zoek een locatie",
|
||||
"success": "Locatie succesvol opgeslagen"
|
||||
"success": "Locatie succesvol opgeslagen",
|
||||
"userCoords": "Jouw locatie: Lat {lat}, Lng {lng}",
|
||||
"userLocationLabel": "Jouw locatie"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
@ -414,9 +414,9 @@
|
||||
"send_you": "stuurt jou"
|
||||
},
|
||||
"usersearch": {
|
||||
"button": "Start het zoeken naar gebruikers...",
|
||||
"headline": "Geografisch zoeken naar gebruikers",
|
||||
"text": "Het maakt niet uit tot welke community je behoort, met het Geo Matching System kun je leden van alle communities vinden op een kaart. Je kunt filteren op aanbiedingen en vereisten en de gebruikers weergeven die aan je profiel voldoen.\n\nDe knop opent een nieuw browservenster waarin de gebruikers in je omgeving op een kaart worden weergegeven.",
|
||||
"button": "Start het zoeken naar gebruikers..."
|
||||
"text": "Het maakt niet uit tot welke community je behoort, met het Geo Matching System kun je leden van alle communities vinden op een kaart. Je kunt filteren op aanbiedingen en vereisten en de gebruikers weergeven die aan je profiel voldoen.\n\nDe knop opent een nieuw browservenster waarin de gebruikers in je omgeving op een kaart worden weergegeven."
|
||||
},
|
||||
"via_link": "via een link",
|
||||
"welcome": "Welkom in de gemeenschap"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
{
|
||||
"100": "%100",
|
||||
"1000thanks": "Bizimle olduğun için 1000lerce teşekkür!",
|
||||
"125": "%125",
|
||||
"85": "%85",
|
||||
"100": "%100",
|
||||
"125": "%125",
|
||||
"1000thanks": "Bizimle olduğun için 1000lerce teşekkür!",
|
||||
"GDD": "GDD",
|
||||
"GDT": "GDT",
|
||||
"PersonalDetails": "Kişisel bilgiler",
|
||||
@ -64,6 +64,7 @@
|
||||
"thanksYouWith": "Sana gönderiyor"
|
||||
},
|
||||
"decay": {
|
||||
"Starting_block_decay": "Blok erimenin başlatılması ",
|
||||
"before_startblock_transaction": "Bu işlem erimeyi içermez.",
|
||||
"calculation_decay": "Erimenin hesaplanması",
|
||||
"calculation_total": "Toplam miktarın hesaplanması",
|
||||
@ -72,7 +73,6 @@
|
||||
"decay_since_last_transaction": "Son işlemden bu yana olan erime",
|
||||
"last_transaction": "Son işlem:",
|
||||
"past_time": "Geçen süre",
|
||||
"Starting_block_decay": "Blok erimenin başlatılması ",
|
||||
"total": "Toplam",
|
||||
"types": {
|
||||
"creation": "Oluşturuldu",
|
||||
@ -123,8 +123,8 @@
|
||||
"lastname": "Soyadı",
|
||||
"memo": "Mesaj",
|
||||
"message": "Mesaj",
|
||||
"new_balance": "Onay sonrası hesap bakiyesi",
|
||||
"newPasswordRepeat": "Yeni şifreyi tekrarla",
|
||||
"new_balance": "Onay sonrası hesap bakiyesi",
|
||||
"no_gdd_available": "Göndermek için GDD'niz yok.",
|
||||
"password": "Şifre",
|
||||
"passwordRepeat": "Şifreyi tekrarla",
|
||||
@ -135,11 +135,11 @@
|
||||
"reset": "Sıfırla",
|
||||
"save": "Kaydet",
|
||||
"scann_code": "<strong>QR Code Scanner</strong> - Ortağınızın QR Kodunu tarayın",
|
||||
"sender": "Gönderen",
|
||||
"send_check": "İşlemi onayla. Lütfen tüm verileri tekrar kontrol et!",
|
||||
"send_now": "Şimdi gönder",
|
||||
"send_transaction_error": "Ne yazık ki işlem gerçekleştirilemedi!",
|
||||
"send_transaction_success": "İşleminiz başarıyla tamamlandı",
|
||||
"sender": "Gönderen",
|
||||
"sorry": "Üzgünüz",
|
||||
"thx": "Teşekkür ederiz",
|
||||
"to": "Geçerlik",
|
||||
@ -257,10 +257,10 @@
|
||||
"communityCoords": "Topluluk Konumunuz: Enlem {lat}, Boylam {lng}",
|
||||
"communityLocationLabel": "Topluluk Konumunuz",
|
||||
"headline": "Kullanıcının Coğrafi Konum Tespiti",
|
||||
"userCoords": "Konumunuz: Enlem {lat}, Boylam {lng}",
|
||||
"userLocationLabel": "Konumunuz",
|
||||
"search": "Konum ara",
|
||||
"success": "Konum başarıyla kaydedildi"
|
||||
"success": "Konum başarıyla kaydedildi",
|
||||
"userCoords": "Konumunuz: Enlem {lat}, Boylam {lng}",
|
||||
"userLocationLabel": "Konumunuz"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
@ -21,7 +21,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"release": "bumpp -r",
|
||||
"version": "auto-changelog -p --commit-limit 0 && git add CHANGELOG.md",
|
||||
"version": "auto-changelog -p --commit-limit 0 && git add CHANGELOG.md && git commit -m \"update changelog\" && git push",
|
||||
"postversion": "git push origin :refs/tags/latest && git tag -f latest && git push origin latest",
|
||||
"installAll": "bun run install",
|
||||
"docker": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml up",
|
||||
"docker:rebuild": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml build",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shared",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"description": "Gradido Shared Code, Low-Level Shared Code, without dependencies on other modules",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
|
||||
@ -4,6 +4,7 @@ export * from './helper'
|
||||
export * from './jwt/JWT'
|
||||
export * from './jwt/payloadtypes/AuthenticationJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/AuthenticationResponseJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/CommandJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/DisburseJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/EncryptedJWEJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/JwtPayloadType'
|
||||
|
||||
22
shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts
Normal file
22
shared/src/jwt/payloadtypes/CommandJwtPayloadType.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { JwtPayloadType } from './JwtPayloadType'
|
||||
|
||||
export class CommandJwtPayloadType extends JwtPayloadType {
|
||||
static COMMAND_TYPE = 'command'
|
||||
|
||||
commandName: string
|
||||
commandClass: string
|
||||
commandArgs: string[]
|
||||
|
||||
constructor(
|
||||
handshakeID: string,
|
||||
commandName: string,
|
||||
commandClass: string,
|
||||
commandArgs: string[],
|
||||
) {
|
||||
super(handshakeID)
|
||||
this.tokentype = CommandJwtPayloadType.COMMAND_TYPE
|
||||
this.commandName = commandName
|
||||
this.commandClass = commandClass
|
||||
this.commandArgs = commandArgs
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user