This commit is contained in:
Lucian I. Last 2025-02-12 08:19:04 +01:00 committed by GitHub
commit 64b1d67f31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 1607 additions and 321 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
dev_database

22
.editorconfig Normal file
View File

@ -0,0 +1,22 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{go,sh}]
indent_style = tab
[{Makefile,Caddyfile}]
indent_style = tab
[LICENSE]
trim_trailing_whitespace = false
insert_final_newline = false

1
.gitignore vendored
View File

@ -14,3 +14,4 @@
# Dependency directories (remove the comment below to include it)
# vendor/
dev_database/

33
Dockerfile Normal file
View File

@ -0,0 +1,33 @@
FROM node:22-alpine AS node
WORKDIR /app
# Download the node dependencies first before adding the rest for caching
COPY ./net/web/package.json ./net/web/yarn.lock ./
RUN yarn --frozen-lockfile
COPY ./net/web/ ./
RUN yarn run build
FROM golang:alpine AS go
EXPOSE 7000
WORKDIR /app/databag
RUN apk add build-base imagemagick sqlite ffmpeg curl
RUN mkdir -p /opt/databag
RUN mkdir -p /var/lib/databag
RUN mkdir -p /app/databag/net
COPY ./net/server /app/databag/net/server
COPY ./net/transform /opt/databag/transform
WORKDIR /app/databag/net/server
RUN go mod download
RUN CGO_ENABLED=1 go build -o databag .
COPY --from=node /app/build /app/databag/net/web/build
ENV DEV=0
ENV ADMIN=password
ENTRYPOINT /app/databag/net/server/entrypoint.sh

20
Makefile Normal file
View File

@ -0,0 +1,20 @@
default:
@grep '^[^#[:space:].].*:' Makefile
dev-start:
docker compose -f docker-compose.dev.yml up -d
dev-stop:
docker compose -f docker-compose.dev.yml down
dev-restart-server:
docker compose -f docker-compose.dev.yml restart net-server
dev-restart-web:
docker compose -f docker-compose.dev.yml restart net-web
dev-restart-repeater:
docker compose -f docker-compose.dev.yml restart net-repeater
prod-docker-start:
docker compose up -d
prod-raw-build:
./build.sh

View File

@ -56,10 +56,24 @@ The app is available on fdroid as well as the google and apple stores. You can t
To use databag, you will need a DNS name pointing to your node with a certificate. You can deploy a node manually, but you will have a much easier time using a container service. Containers for arm64 and amd64 are available [here](https://hub.docker.com/r/balzack/databag/tags).
### Docker Compose Command
### Docker Compose
From the net/container sub directory:
- sudo docker-compose -f compose.yaml -p databag up
Launch with dockerhub container using docker compose:
#### Standard launch
```shell
# From the net/container sub directory:
docker-compose -f compose.yaml -p databag up
```
#### Launch with certbot https certificate
```shell
# FIRST: create a DNS entry in your DNS to point your desired subdomain to your host
# SECOND: edit the net/container/docker-compose-swag.yml to include your domain name
# THIRD: From the root of the project directory:
mkdir -p ~/appdata
docker-compose -f net/container/docker-compose-swag.yml -p databag up
```
### Example with Portainer and Nginx Proxy Manager
@ -154,4 +168,4 @@ If you want to enable audio and video calls, you should setup your own relay ser
### Roadmap
Please let me know any missing features; [here](/doc/backlog.md) is the current backlog. Features are prioritized based on interest from the community.
Please add any missing features; [here](/doc/backlog.md) is the current backlog. Features are prioritized based on interest from the community.

2
app/mobile/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
.DS_Store

View File

@ -10,12 +10,18 @@ export async function setCardConnecting(server, token, cardId) {
}
export async function setCardConnected(server, token, cardId, access, view, article, channel, profile) {
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
const protocol = insecure ? 'http' : 'https';
let card = await fetchWithTimeout(`${protocol}://${server}/contact/cards/${cardId}/status?agent=${token}&token=${access}&viewRevision=${view}&articleRevision=${article}&channelRevision=${channel}&profileRevision=${profile}`, { method: 'PUT', body: JSON.stringify('connected') } );
checkResponse(card);
return await card.json();
}
export async function setCardConfirmed(server, token, cardId) {
const insecure = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|:\d+$|$)){4}$/.test(server);
const protocol = insecure ? 'http' : 'https';
let card = await fetchWithTimeout(`${protocol}://${server}/contact/cards/${cardId}/status?agent=${token}`, { method: 'PUT', body: JSON.stringify('confirmed') } );
checkResponse(card);
return await card.json();

View File

@ -206,7 +206,7 @@ const Strings = [
selectTopic: 'Select Topic for Sharing',
mfaTitle: 'Multi-Factor Authentication',
mfaSteps: 'Store the SHA256 secret and confirm the verification code',
mfaSteps: 'Store the secret and confirm the verification code',
mfaError: 'verification code error',
mfaDisabled: 'verification temporarily disabled',
mfaConfirm: 'Confirm',
@ -417,7 +417,7 @@ const Strings = [
selectTopic: 'Choisissez le sujet à partager',
mfaTitle: 'Authentification Multi-Factor',
mfaSteps: 'Enregistrez le secret SHA256 et confirmez le code de vérification',
mfaSteps: 'Enregistrez le secret et confirmez le code de vérification',
mfaEnter: 'Entrez votre code de vérification',
mfaError: 'erreur de code de vérification',
mfaDisabled: 'vérification temporairement désactivée',
@ -629,7 +629,7 @@ const Strings = [
selectTopic: 'Elija un tema para compartir',
mfaTitle: 'Autenticación de Dos Factores',
mfaSteps: 'Guarde el secreto SHA256 y confirme el código de verificación',
mfaSteps: 'Guarde el secreto y confirme el código de verificación',
mfaEnter: 'Ingresa tu código de verificación',
mfaError: 'error de código de verificación',
mfaDisabled: 'verificación temporalmente deshabilitada',
@ -641,18 +641,18 @@ const Strings = [
},
{
languageCode: 'de',
visibleRegistry: 'Sichtbar in der Registrierung',
visibleRegistry: 'Im Verzeichnis sichtbar',
edit: 'Bearbeiten',
enableNotifications: 'Mitteilungen',
allowUnsealed: 'Unsichere Themen',
sealedTopics: 'Gesicherte Themen',
colorMode: 'Farmodus',
allowUnsealed: 'Unversiegelte Themen ermöglichen',
sealedTopics: 'Versiegelte Themen',
colorMode: 'Farbmodus',
hourMode: 'Stunde',
dateMode: 'Datum',
language: 'Sprache',
logout: 'Ausloggen',
changeLogin: 'Kennwort Aktualisieren',
deleteAccount: 'Konto Löschen',
changeLogin: 'Kennwort aktualisieren',
deleteAccount: 'Konto löschen',
contacts: 'Kontakte',
topics: 'Themen',
messages: 'Mitteilungen',
@ -663,8 +663,8 @@ const Strings = [
messages: 'Nachrichtenübermittlung',
timeFull: '24h',
timeHalf: '12h',
monthStart: 'mm/dd',
monthEnd: 'dd/mm',
monthStart: 'mm/tt',
monthEnd: 'tt/mm',
error: 'Fehler',
tryAgain: 'Bitte versuche es erneut.',
@ -681,33 +681,33 @@ const Strings = [
removeSeal: 'Sicherheitsschlüssel entfernen',
disableSeal: 'Sicherheitsschlüssel deaktivieren',
unlockSeal: 'Sicherheitsschlüssel entsperren',
typeDelete: 'Geben Sie [löschen]',
typeDelete: 'Bitte geben Sie [löschen] ein',
deleteKey: 'löschen',
enableTopics: 'Aktivieren Sie gesicherte Themen',
manageTopics: 'Sicherheitsschlüssel verwalten',
enableTopics: 'Aktivieren Sie versiegelte Themen',
manageTopics: 'Versiegelungsschlüssel verwalten',
changePassword: 'Ändern Sie das Passwort des Sicherheitsschlüssels',
update: 'Aktualisieren',
changeKey: 'Schlüsselpasswort ändern',
delayMessage: 'Die Schlüsselgenerierung kann mehrere Minuten dauern.',
changeMessage: 'Hier können Sie den Benutzernamen und/oder das Passwort für Ihr Konto ändern.',
cancel: 'Stornieren',
cancel: 'Abbrechen',
confirmlogout: 'Ausloggen',
loggingOut: 'Abmelden bestätigen',
username: 'Nutzername',
save: 'Speichern',
notAvailable: 'Benutzername Nicht Verfügbar',
notAvailable: 'Benutzername nicht verfügbar',
blockedContacts: 'Blockierte Kontakte',
restoreContact: 'Kontakt Wiederherstellen?',
restoreContact: 'Kontakt wiederherstellen?',
blockedTopics: 'Blockierte Themen',
restoreTopic: 'Thema Wiederherstellen?',
restoreTopic: 'Thema wiederherstellen?',
blockedMessages: 'Blockierte Nachrichten',
restoreMessage: 'Nachricht Wiederherstellen?',
restoreMessage: 'Nachricht wiederherstellen?',
close: 'Schließen',
ok: 'OK',
noBlockedContacts: 'Keine Blockierten Kontakte',
noBlockedTopics: 'Keine Blockierten Themen',
noBlockedMessages: 'Keine Blockierten Nachrichten',
noBlockedContacts: 'Keine blockierten Kontakte',
noBlockedTopics: 'Keine blockierten Themen',
noBlockedMessages: 'Keine blockierten Nachrichten',
restore: 'Wiederherstellen',
//profile page
@ -715,20 +715,20 @@ const Strings = [
name: 'Name',
location: 'Standort',
description: 'Beschreibung',
registryVisible: 'Sichtbar in der Registrierung',
registryVisible: 'Im Verzeichnis sichtbar',
editImage: 'Bild Bearbeiten',
editDetails: 'Details Bearbeiten',
//contacts page
back: 'Rückwärts',
deleteContact: 'Kontakt Löschen',
back: 'Zurück',
deleteContact: 'Kontakt löschen',
confirmDelete: 'Löschen',
disconnectContact: 'Kontakt Trennen',
disconnectContact: 'Kontakt trennen',
confirmDisconnect: 'Trennen',
blockContact: 'Kontakt Ausblenden',
confirmBlock: 'Verstecken',
reportContact: 'Kontakt Melden',
confirmReport: 'Bericht',
blockContact: 'Kontakt blockieren',
confirmBlock: 'Blockieren',
reportContact: 'Kontakt melden',
confirmReport: 'Melden',
confirmed: 'Gerettet',
pending: 'Unbekannt',
connecting: 'Verbinden',
@ -740,20 +740,20 @@ const Strings = [
actionConnect: 'Verbinden',
actionAccept: 'Akzeptieren',
actionSave: 'Speichern',
actionCancel: 'Stornieren',
actionCancel: 'Abbrechen',
actionDisconnect: 'Trennen',
actionIgnore: 'Ignorieren',
actionDelete: 'Löschen',
actionBlock: 'Verstecken',
actionReport: 'Bericht',
actionBlock: 'Blockieren',
actionReport: 'Melden',
// contact list page
add: 'Hinzufügen',
contactFilter: 'Kontakte',
serverFilter: 'Server',
usernameFilter: 'Benutzername',
viewProfile: 'Profil Anzeigen',
messageContact: 'Nachricht Senden',
viewProfile: 'Profil anzeigen',
messageContact: 'Nachricht senden',
callContact: 'Kontakt Anrufen',
noContacts: 'Keine Kontakte Gefunden',
@ -762,8 +762,8 @@ const Strings = [
contacts: 'Kontakte',
topics: 'Themen',
subject: 'Titel (optional)',
create: 'Erstellen',
sealed: 'Gesichert',
create: 'Starten',
sealed: 'versiegeln',
newTopic: 'Neues Thema',
new: 'Neu',
@ -776,9 +776,9 @@ const Strings = [
editSubject: 'Titel bearbeiten',
topicMembers: 'Themenmitglieder',
leaveTopic: 'Verlasse das Thema',
deleteTopic: 'Das Thema Löschen',
blockTopic: 'Blockiere das Thema',
reportTopic: 'Das Thema Melden',
deleteTopic: 'Das Thema löschen',
blockTopic: 'Das Thema blockieren',
reportTopic: 'Das Thema melden',
unknown: 'Unbekannt',
accounts: 'Konten',
@ -789,8 +789,8 @@ const Strings = [
federatedHost: 'Verbundname',
storageLimit: 'Raum (GB) / Konto',
keyType: 'Schlüsselart',
enableImage: 'Bilddateien Aktivieren',
enableAudio: 'Audiodateien Aktivieren',
enableImage: 'Bilddateien aktivieren',
enableAudio: 'Audiodateien aktivieren',
enableVideo: 'Videodateien aktivieren',
enableBinary: 'Binärdateien aktivieren',
enableCalls: 'Anrufe Ermöglichen',
@ -823,14 +823,14 @@ const Strings = [
editMessage: 'Nachrichtentext Bearbeiten',
emptyTopic: 'Keine Nachrichten',
notes: 'Anmerkungen',
notes: 'Notizen',
noTopics: 'Keine Themen',
welcome: 'Willkommen bei Databag',
communication: 'Kommunikation für das dezentrale Internet',
setup: 'Richten Sie Ihr Profil ein',
connect: 'Verbinde dich mit Menschen',
start: 'Eine Konversation Beginnen',
start: 'Eine Konversation beginnen',
started: 'Loslegen',
deleteMessage: 'Nachricht Löschen',
@ -841,7 +841,7 @@ const Strings = [
selectTopic: 'Wählen Sie ein Thema zum Teilen aus',
mfaTitle: 'Zwei-Faktor-Authentifizierung',
mfaSteps: 'Speichern Sie das SHA256-Geheimnis und bestätigen Sie den Bestätigungscode',
mfaSteps: 'Speichern Sie das Geheimnis und bestätigen Sie den Bestätigungscode',
mfaEnter: 'Geben Sie Ihren Bestätigungs-Code ein',
mfaError: 'Verifizierungscodefehler',
mfaDisabled: 'Verifizierung vorübergehend deaktiviert',
@ -1038,7 +1038,7 @@ const Strings = [
selectTopic: 'Escolha o tópico para compartilhar',
mfaTitle: 'Autenticação de Dois Fatores',
mfaSteps: 'Salve o segredo SHA256 e confirme o código de verificação',
mfaSteps: 'Salve o segredo e confirme o código de verificação',
mfaEnter: 'Digite seu código de verificação',
mfaError: 'erro de código de verificação',
mfaDisabled: 'verificação temporariamente desativada',
@ -1233,7 +1233,7 @@ const Strings = [
selectTopic: 'Выберите тему для обмена',
mfaTitle: 'Двухфакторная аутентификация',
mfaSteps: 'Сохраните секрет SHA256 и подтвердите код подтверждения',
mfaSteps: 'Сохраните секрет и подтвердите код подтверждения',
mfaEnter: 'Введите Ваш верификационный код',
mfaError: 'ошибка проверочного кода',
mfaDisabled: 'проверка временно отключена',
@ -1242,7 +1242,222 @@ const Strings = [
disable: 'Отключить',
confirmDisable: 'Отключение двухфакторной аутентификации',
disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?',
}
},
{
// settings screen
languageCode: 'el',
visibleRegistry: 'Ορατός Λογαριασμός στο Μητρώο του Διακομιστή',
edit: 'Επεξεργασία',
enableNotifications: 'Ειδοποιήσεις',
allowUnsealed: 'Επιτρέψτε μη σφραγισμένες συζητήσεις',
sealedTopics: 'Σφραγισμένες Συζητήσεις',
colorMode: 'Λειτουργία Χρώματος',
hourMode: 'Ώρα',
dateMode: 'Ημερομηνία',
language: 'Γλώσσα',
logout: 'Αποσύνδεση',
changeLogin: 'Αλλαγή Στοιχείων Σύνδεσης',
deleteAccount: 'Διαγραφή Λογαριασμού',
contacts: 'Επαφές',
topics: 'Συζητήσεις',
messages: 'Μηνύματα',
support: 'Υποστήριξη',
blocked: 'Μπλοκαρισμένα',
account: 'Λογαριασμός',
display: 'Μορφοποίηση',
messaging: 'Μηνύματα',
timeFull: '24ωρο',
timeHalf: '12ωρο',
monthStart: 'μμ/ηη',
monthEnd: 'ηη/μμ',
error: 'Σφάλμα',
tryAgain: 'Προσπαθήστε ξανά.',
// seal wizard
sealUnset: 'Δημιουργήστε ένα κλειδί για να ενεργοποιήσετε την κρυπτογράφηση από άκρη σε άκρη (E2EE).',
sealUnlocked: 'Απενεργοποιώντας το κλειδί σφράγισης, δεν θα έχετε πρόσβαση σε κρυπτογραφημένες συζητήσεις σε αυτήν τη συσκευή μέχρι να το ενεργοποιήσετε ξανά.',
sealLocked: 'Ξεκλειδώστε το κλειδί σφράγισης για να υποστηρίξετε κρυπτογραφημένες συζητήσεις σε αυτήν τη συσκευή.',
sealDelete: 'Η διαγραφή του κλειδιού θα αφαιρέσει οριστικά την πρόσβαση σε κρυπτογραφημένες συζητήσεις για ΟΛΕΣ τις συσκευές σας.',
password: 'Κωδικός Πρόσβασης',
confirmPassword: 'Επιβεβαίωση Κωδικού',
generate: 'Δημιουργία',
disable: 'Απενεργοποίηση',
delete: 'Διαγραφή',
unlock: 'Ξεκλείδωμα',
removeSeal: 'Αφαίρεση Κλειδιού Σφράγισης',
disableSeal: 'Απενεργοποίηση Κλειδιού Σφράγισης',
unlockSeal: 'Ξεκλείδωμα Κλειδιού Σφράγισης',
typeDelete: 'Πληκτρολογήστε [διαγραφή]',
deleteKey: 'διαγραφή',
enableTopics: 'Ενεργοποίηση Σφραγισμένων Συζητήσεων',
manageTopics: 'Διαχείριση Κλειδιού Σφράγισης',
changePassword: 'Αλλαγή κωδικού κλειδιού σφράγισης.',
update: 'Αλλαγή',
changeKey: 'Αλλαγή Κωδικού Κλειδιού',
delayMessage: 'Η δημιουργία κλειδιού μπορεί να διαρκέσει αρκετά λεπτά.',
changeMessage: 'Εδώ μπορείτε να αλλάξετε το όνομα χρήστη και/ή τον κωδικό πρόσβασης για το λογαριασμό σας.',
// settings modals
cancel: 'Ακύρωση',
confirmLogout: 'Αποσύνδεση',
loggingOut: 'Αποσύνδεση',
username: 'Όνομα Χρήστη',
save: 'Αποθήκευση',
notAvailable: 'Το όνομα χρήστη δεν είναι διαθέσιμο',
blockedContacts: 'Μπλοκαρισμένες Επαφές',
restoreContact: 'Επαναφορά Επαφής;',
blockedTopics: 'Μπλοκαρισμένες Συζητήσεις',
restoreTopic: 'Επαναφορά Συζήτησης;',
blockedMessages: 'Μπλοκαρισμένα Μηνύματα',
restoreMessage: 'Επαναφορά Μηνύματος;',
close: 'Κλείσιμο',
ok: 'Εντάξει',
noBlockedContacts: 'Δεν υπάρχουν μπλοκαρισμένες επαφές',
noBlockedTopics: 'Δεν υπάρχουν μπλοκαρισμένες συζητήσεις',
noBlockedMessages: 'Δεν υπάρχουν μπλοκαρισμένα μηνύματα',
restore: 'Επαναφορά',
// profile page
edit: 'Επεξεργασία',
name: 'Όνομα',
location: 'Τοποθεσία',
description: 'Περιγραφή',
registryVisible: 'Ορατό στο Μητρώο',
editImage: 'Επεξεργασία Εικόνας',
editDetails: 'Επεξεργασία Λεπτομερειών',
// contacts page
back: 'Πίσω',
deleteContact: 'Διαγραφή Επαφής',
confirmDelete: 'Διαγραφή',
disconnectContact: 'Αποσύνδεση Επαφής',
confirmDisconnect: 'Αποσύνδεση',
blockContact: 'Μπλοκάρισμα Επαφής',
confirmBlock: 'Μπλοκάρισμα',
reportContact: 'Αναφορά Επαφής',
confirmReport: 'Αναφορά',
confirmed: 'Αποθηκεύτηκε',
pending: 'Άγνωστο',
connecting: 'Αίτημα Στάλθηκε',
connected: 'Συνδέθηκε',
requested: 'Λήψη Αιτήματος',
unsaved: 'Μη Αποθηκευμένο',
offsync: 'Εκτός Συγχρονισμού',
actionResync: 'Επανασυγχρονισμός',
actionConnect: 'Σύνδεση',
actionAccept: 'Αποδοχή',
actionSave: 'Αποθήκευση',
actionCancel: 'Ακύρωση',
actionDisconnect: 'Αποσύνδεση',
actionIgnore: 'Παράβλεψη',
actionDelete: 'Διαγραφή',
actionBlock: 'Μπλοκάρισμα',
actionReport: 'Αναφορά',
actionLeave: 'Αποχώρηση',
// contact list page
add: 'Προσθήκη',
contactFilter: 'Επαφές',
serverFilter: 'Διακομιστής',
usernameFilter: 'Όνομα Χρήστη',
viewProfile: 'Προβολή Προφίλ',
messageContact: 'Αποστολή Μηνύματος',
callContact: 'Κλήση Επαφής',
noContacts: 'Δεν βρέθηκαν Επαφές',
// channels list
profile: 'Προφίλ',
contacts: 'Επαφές',
topics: 'Συζητήσεις',
subject: 'Θέμα (προαιρετικό)',
create: 'Δημιουργία',
sealed: 'Σφραγισμένο',
newTopic: 'Νέο Θέμα',
new: 'Νέο',
// details
topic: 'Συζήτηση',
host: 'οικοδεσπότης',
guest: 'επισκέπτης',
leave: 'Αποχώρηση',
members: 'Μέλη',
editSubject: 'Επεξεργασία Θέματος',
topicMembers: 'Μέλη Συζήτησης',
leaveTopic: 'Αποχώρηση από τη Συζήτηση',
deleteTopic: 'Διαγραφή Συζήτησης',
blockTopic: 'Μπλοκάρισμα Συζήτησης',
reportTopic: 'Αναφορά Συζήτησης',
unknown: 'άγνωστο',
accounts: 'Λογαριασμοί',
createAccount: 'Δημιουργία Λογαριασμού',
accessAccount: 'Πρόσβαση Λογαριασμού',
token: 'Token',
settings: 'Ρυθμίσεις',
federatedHost: 'Ομοσπονδιακός Οικοδεσπότης',
storageLimit: 'Όριο Αποθήκευσης (GB) / Λογαριασμό',
keyType: 'Τύπος Κλειδιού Λογαριασμού',
enableImage: 'Ενεργοποίηση Ουράς Εικόνας',
enableAudio: 'Ενεργοποίηση Ουράς Ήχου',
enableVideo: 'Ενεργοποίηση Ουράς Βίντεο',
enableBinary: 'Ενεργοποίηση υπόλοιπων Ψηφιακών Αρχείων',
enableCalls: 'Ενεργοποίηση Κλήσεων WebRTC',
iceService: 'Υπηρεσία Cloudflare',
relayUrl: 'URL Αναμετάδοσης',
relayUsername: 'Όνομα Χρήστη Αναμετάδοσης',
relayPassword: 'Κωδικός Αναμετάδοσης',
newMessage: 'Νέο Μήνυμα',
fontSize: 'Μέγεθος Γραμματοσειράς',
small: 'Μικρό',
medium: 'Μεσαίο',
large: 'Μεγάλο',
fontColor: 'Χρώμα Γραμματοσειράς',
selectedColor: 'Επιλεγμένο Χρώμα',
login: 'Σύνδεση',
createAccount: 'Δημιουργία Λογαριασμού',
forgotPassword: 'Ξεχάσατε τον Κωδικό;',
adminAccess: 'Πρόσβαση Διαχειριστή',
server: 'Διακομιστής',
access: 'Πρόσβαση',
defaultPublic: 'Ο προεπιλεγμένος δημόσιος διακομιστής είναι μόνο για δοκιμές. Χρησιμοποιήστε ιδιωτικό διακομιστή σε διαφορετική περίπτωση.',
confirmPassword: 'Επιβεβαίωση Κωδικού',
accountLogin: 'Σύνδεση Λογαριασμού',
accessAccount: 'Πρόσβαση Λογαριασμού',
agree: 'Συμφωνώ με τους Όρους Χρήσης',
terms: 'Προβολή Όρων Χρήσης',
policy: 'Όροι Χρήσης και Πολιτική Χρήστη',
editMessage: 'Επεξεργασία Κειμένου Μηνύματος',
emptyTopic: 'Κενό Θέμα',
noTopics: 'Δεν υπάρχουν Συζητήσεις',
notes: 'Σημειώσεις',
welcome: 'Καλώς Ήρθατε στο Databag',
communication: 'Επικοινωνία για τον Αποκεντρωμένο Ιστό',
setup: 'Ρυθμίστε το Προφίλ σας',
connect: 'Συνδεθείτε με Άτομα',
start: 'Ξεκινήστε μια Συνομιλία',
started: 'Ξεκινήστε',
deleteMessage: 'Διαγραφή Μηνύματος',
blockMessage: 'Μπλοκάρισμα Μηνύματος',
reportMessage: 'Αναφορά Μηνύματος',
select: 'Επιλογή',
selectTopic: 'Επιλέξτε Συζήτηση για Κοινή Χρήση',
mfaTitle: 'Επαλήθευση Πολλαπλών Παραγόντων (MFA)',
mfaSteps: 'Αποθηκεύστε το μυστικό κωδικό και επιβεβαιώστε τον κωδικό επαλήθευσης',
mfaError: 'σφάλμα κωδικού επαλήθευσης',
mfaDisabled: 'η επαλήθευση είναι προσωρινά απενεργοποιημένη',
mfaConfirm: 'Επιβεβαίωση',
mfaEnter: 'Εισαγάγετε τον κωδικό επαλήθευσης σας',
disable: 'Απενεργοποίηση',
confirmDisable: 'Απενεργοποίηση Επαλήθευσης Πολλαπλών Παραγόντων ',
disablePrompt: 'Είστε σίγουροι ότι θέλετε να απενεργοποιήσετε την επαλήθευση πολλαπλών παραγόντων;',
}
];
export function getLanguageStrings() {
@ -1268,5 +1483,8 @@ export function getLanguageStrings() {
if (lang === 'ru') {
return Strings[5];
}
if (lang === 'el') {
return Strings[6];
}
return Strings[0];
};

View File

@ -279,20 +279,14 @@ export function useTopicItem(item, hosting, remove, contentKey) {
};
const clickableText = (text) => {
const urlPatternn = new RegExp('^(https?:\\/\\/)?'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
const urlPattern = new RegExp('(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)');
const hostPattern = new RegExp('^https?:\\/\\/', 'i');
let clickable = [];
let group = '';
const words = text == null ? [''] : text.split(' ');
words.forEach((word, index) => {
if (!!urlPatternn.test(word)) {
if (!!urlPattern.test(word)) {
clickable.push(<Text key={index}>{ group }</Text>);
group = '';
const url = !!hostPattern.test(word) ? word : `https://${word}`;

View File

@ -2,6 +2,16 @@
# Backlog
**Call UI:** pop-up window does not appear outside the app, nor does it activate the lock screen for interaction.
**Call UI:** The notification for an incoming call sends two alerts using the message notification sound and then stops ringing.
**Call UI:** Notifications from the notification bar either open on the first tap, the second, or, at most, the third consecutive tap.
**Call UI:** There is no "waiting to answer" sound until the recipient accepts the call and the proximity sensor engages after the call is been answered.
**Call UI:** There is no timer to track the call duration.
**SDK:** refactor mobile and browser code into an SDK and app
**Typescript:** refactor mobile and browser apps to use typescript
@ -14,8 +24,6 @@
**Embed STUN/TURN:** directly support nat translation for webrtc as part of the server
**Mobile HTTP:** support plaintext communcation with server from mobile app
**More Languages:** add any additional language requests
**CLI:** create a CLI client for automation
@ -24,8 +32,6 @@
**Preferred Nodes:** add an admin managed list of other nodes and present the nodes in a select box when searching for contacts
**2-Factor Auth:** add 2-factor auth capabilities
**SSO:** support single sign on
**Audio & Video Message:** support recording and sending audio and video messages
@ -56,6 +62,8 @@
**APN:** avoid firebase for iOS and use APN directly
**Password Recover:** add a mechanism for password recovery based on shared secret
**Auto Archive:** add ability to auto delete old data
**1-1 Sealed:** admin config for defaulting to sealed topic when starting a 1-1 topic from the contact list

View File

@ -23,7 +23,7 @@ These instructions assume you have the following setup:
apt-get -y install curl<br/>
apt-get -y install net-tools<br/>
apt-get -y install jq<br/>
apt-get -y install netcat<br/>
apt-get -y install netcat-openbsd<br/>
apt-get -y install unzip<br/>
apt-get -y install wget<br/>
apt-get -y install git<br/>

37
docker-compose.dev.yml Normal file
View File

@ -0,0 +1,37 @@
name: databag-dev
services:
net-web:
build:
context: ./net/web
dockerfile: Dockerfile.dev
working_dir: /app
volumes:
- ./net/web:/app
command: sh -c "yarn && chokidar '**/*.js' '**/*.ts' -c 'yarn run build' --debounce 18000 --initial --ignore node_modules --ignore build"
net-server:
build:
context: ./net/server
dockerfile: Dockerfile.dev
ports:
- 127.0.0.1:7000:7000
volumes:
- ./net/server:/app/databag/net/server
- ./dev_database:/var/lib/databag
- ./net/transform:/opt/databag/transform
- ./net/web/build:/app/databag/net/web/build
working_dir: /app
environment:
- ADMIN=password
- DEV=1
command: /app/databag/net/server/entrypoint.sh
net-repeater:
build:
context: ./net/repeater
dockerfile: Dockerfile.dev
working_dir: /app
volumes:
- ./net/repeater:/app
ports:
- 127.0.0.1:7878:7878
command: go run main.go

14
docker-compose.yml Normal file
View File

@ -0,0 +1,14 @@
name: databag
services:
app:
build: .
ports:
- 127.0.0.1:7000:7000
volumes:
- database:/var/lib/databag
environment:
- ADMIN=password
volumes:
database:

View File

@ -0,0 +1,14 @@
name: databag-example-http
services:
app:
image: balzack/databag:latest
restart: unless-stopped
ports:
- 127.0.0.1:7000:7000
volumes:
- database:/var/lib/databag
environment:
- ADMIN=password
volumes:
database:

View File

@ -0,0 +1,3 @@
example.com {
reverse_proxy http://app:443
}

View File

@ -0,0 +1,29 @@
name: databag-example-ssl
services:
caddy:
image: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
# Edit the Caddyfile and replace "example.com" with your own domain
- ./Caddyfile:/etc/caddy/Caddyfile:ro
# Recommended by Caddy
- caddy_data:/data
- caddy_config:/config
depends_on:
- app
app:
image: balzack/databag:latest
restart: unless-stopped
volumes:
- database:/var/lib/databag
environment:
- ADMIN=password
- DATABAG_PORT=443
volumes:
database:
caddy_data:
caddy_config:

View File

@ -0,0 +1,17 @@
[Unit]
Description=databag server
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=databag
ExecStart=/app/databag/net/server/entrypoint.sh
# [Service]
# Environment="ADMIN=password"
[Install]
WantedBy=multi-user.target

85
examples/linux/install.sh Executable file
View File

@ -0,0 +1,85 @@
#!/bin/bash
set -e
function confirm() {
read -p "Are you sure you want to continue? [Y/n] " reply
if [ "$reply" != "Y" ] && [ "$reply" != "y" ]; then
echo "Aborting"
exit 1
fi
}
if ! id "databag" >/dev/null 2>&1; then
echo "User databag not found, creating..."
confirm
sudo useradd databag
fi
if [[ ! -d "/app/databag" ]]; then
echo "Creating app directory for databag, this requires sudo permissions"
sudo rm -rf /app/databag || true
sudo mkdir -p /app/databag
echo "Downloading databag repository into /app/databag"
git clone --depth 1 https://github.com/balzack/databag.git /app/databag
sudo chown -R databag:databag /app/databag
fi
cd /app/databag
# You might be running this script from the root of this repository
if [[ "/app/databag" != $(pwd) ]]; then
cd ../..
fi
if [[ "/app/databag" != $(pwd) ]]; then
echo "Install databag must be done from /app/databag"
echo "Please re-clone the github repository into /app/databag, like so:"
echo "mkdir -p /app; https://github.com/balzack/databag /app/databag"
exit 1
fi
if [[ -z $(which yarn) ]]; then
echo "Yarn is not installed, installing..."
npm install --global yarn
fi
echo "Building frontend files..."
cd net/web
yarn --frozen-lockfile
echo "Removing old frontend files, requires sudo permissions..."
sudo rm -rf build
yarn run build
sudo chown -R databag:databag build
cd ../..
echo "Building backend files..."
cd net/server
CGO_ENABLED=1 go build -o databag .
cd ../..
echo "Creating databag locations..."
sudo mkdir -p /opt/databag
sudo mkdir -p /var/lib/databag
sudo chown -R databag:databag /var/lib/databag
echo "Copying transform scripts..."
sudo mkdir -p /opt/databag/transform
sudo cp net/transform/*.sh /opt/databag/transform/
sudo chmod +x /opt/databag/transform/*.sh
if [[ ! -f /etc/systemd/system/databag.service ]]; then
function createService() {
echo "Creating databag service, requires sudo permissions..."
sudo cp examples/linux/databag.service /etc/systemd/system/databag.service
sudo chmod 664 /etc/systemd/system/databag.service
sudo systemctl daemon-reload
}
function startService() {
echo "Starting databag service..."
sudo systemctl start databag.service
}
createService $? "Failed to install databag service"
startService $? "Failed to start databag service"
fi
echo ""
echo "Done"

29
examples/linux/uninstall.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
function confirm() {
read -p "Are you sure you want to continue? [Y/n] " reply
if [ "$reply" != "Y" ] && [ "$reply" != "y" ]; then
echo "Aborting"
exit 1
fi
}
echo "Stopping, disabling and removing databag service..."
confirm
sudo systemctl stop databag.service
sudo systemctl disable databag.service
sudo rm /etc/systemd/system/databag.service
sudo systemctl reload
echo "Removing databag data..."
confirm
sudo rm -rf /app/databag /opt/databag /var/lib/databag
if [ -z "$(ls -A /app)" ]; then
sudo rmdir /app
fi
echo "Removing databag user..."
confirm
sudo userdel databag
echo "Done"

View File

@ -1,67 +0,0 @@
FROM ubuntu:20.04 as build
ARG TARGETPLATFORM
LABEL maintainer="roland.osborne@gmail.com"
EXPOSE 7000
ENV TZ=America/Los_Angeles
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update
RUN apt-get -y install curl net-tools jq netcat unzip wget git vim fail2ban imagemagick-6.q16 ffmpeg build-essential sqlite3 npm
RUN apt-get -y upgrade
RUN npm install --global yarn
RUN npm install -g n
RUN n stable
RUN mkdir /app
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then ARCHITECTURE=amd64; elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCHITECTURE=arm64; elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCHITECTURE=aarch64; else ARCHITECTURE=unsupported; fi \
&& wget -P /app https://go.dev/dl/go1.22.2.linux-${ARCHITECTURE}.tar.gz \
&& tar -C /usr/local -xzf /app/go1.22.2.linux-${ARCHITECTURE}.tar.gz
RUN git clone https://github.com/balzack/databag.git /app/databag
RUN yarn config set network-timeout 300000
RUN yarn --cwd /app/databag/net/web install
RUN yarn --cwd /app/databag/net/web build
RUN cd /app/databag/net/server; /usr/local/go/bin/go build databag
RUN mkdir /opt/databag
ADD transform /opt/databag/transform
RUN mkdir -p /var/lib/databag
RUN echo 'export PATH=$PATH:/usr/local/go/bin' >> /root/.bashrc
RUN echo "set expandtab\nset tabstop=2\nset softtabstop=2\nset shiftwidth=2\nset encoding=utf-8\nset fileencoding=utf-8\n" > /root/.vimrc
RUN echo "bind 'set mark-symlinked-directories on'" >> /root/.bashrc
ADD entrypoint.sh /app
ADD dev_setup.sh /app
RUN rm -rf /usr/local/go
RUN rm -rf /root/go
RUN rm -rf /app/go*
RUN rm -rf /root/.cache/go*
RUN yarn cache clean
RUN rm -rf /app/databag/app
RUN rm -rf /app/databag/net/web/node_modules
RUN n prune
RUN npm uninstall -g n
RUN rm -rf /usr/local/n
RUN rm -rf /usr/local/bin/node
RUN apt-get -y remove git build-essential npm vim nodejs linux-libc-dev
RUN rm -rf /var/lib/apt/lists
FROM scratch
COPY --from=build / /
ENTRYPOINT ["/app/entrypoint.sh"]

View File

@ -1,50 +0,0 @@
FROM ubuntu:20.04 as build
ARG TARGETPLATFORM
LABEL maintainer="roland.osborne@gmail.com"
EXPOSE 7000
ENV TZ=America/Los_Angeles
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update
RUN apt-get -y install curl net-tools jq netcat unzip wget git vim fail2ban imagemagick-6.q16 ffmpeg build-essential sqlite3 npm
RUN apt-get -y upgrade
RUN npm install --global yarn
RUN npm install -g n
RUN n stable
RUN mkdir /app
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then ARCHITECTURE=amd64; elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCHITECTURE=arm64; elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then ARCHITECTURE=aarch64; else ARCHITECTURE=unsupported; fi \
&& wget -P /app https://go.dev/dl/go1.22.2.linux-${ARCHITECTURE}.tar.gz \
&& tar -C /usr/local -xzf /app/go1.22.2.linux-${ARCHITECTURE}.tar.gz
RUN git clone https://github.com/balzack/databag.git /app/databag
RUN yarn config set network-timeout 300000
RUN yarn --cwd /app/databag/net/web install
RUN yarn --cwd /app/databag/net/web build
RUN cd /app/databag/net/server; /usr/local/go/bin/go build databag
RUN mkdir /opt/databag
ADD transform /opt/databag/transform
RUN mkdir -p /var/lib/databag
RUN echo 'export PATH=$PATH:/usr/local/go/bin' >> /root/.bashrc
RUN echo "set expandtab\nset tabstop=2\nset softtabstop=2\nset shiftwidth=2\nset encoding=utf-8\nset fileencoding=utf-8\n" > /root/.vimrc
RUN echo "bind 'set mark-symlinked-directories on'" >> /root/.bashrc
ADD dev_setup.sh /app
ADD entrypoint.sh /app
FROM scratch
COPY --from=build / /
ENTRYPOINT ["/app/entrypoint.sh"]

View File

@ -1,20 +0,0 @@
cd /root
wget -P /app https://go.dev/dl/go1.22.2.linux-amd64.tar.gz
tar -C /usr/local -xzf /app/go1.22.2.linux-amd64.tar.gz
apt-get update
apt-get -y install git build-essential npm vim
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt install -y nodejs
npm install --global yarn
npm install -g n
n stable
cd /app/databag
git checkout .
yarn --cwd /app/databag/net/web install
yarn --cwd /app/databag/net/web build
cd /app/databag/net/server; /usr/local/go/bin/go build databag

View File

@ -1,12 +0,0 @@
version: "3.9"
services:
databag:
environment:
- DEV=1
container_name: databag
image: balzack/databag:latest
ports:
- "7000:7000"
volumes:
- ./databag-data:/var/lib/databag

View File

@ -1,10 +0,0 @@
version: "3.9"
services:
databag:
container_name: databag
image: balzack/databag:latest
ports:
- "7000:7000"
volumes:
- ./databag-data:/var/lib/databag

View File

@ -1,25 +0,0 @@
#!/bin/bash
set -e
sqlite3 /var/lib/databag/databag.db "VACUUM;"
sqlite3 /var/lib/databag/databag.db "CREATE TABLE IF NOT EXISTS 'configs' ('id' integer NOT NULL UNIQUE,'config_id' text NOT NULL,'str_value' text,'num_value' integer,'bool_value' numeric,'bin_value' blob,PRIMARY KEY ('id'));"
sqlite3 /var/lib/databag/databag.db "CREATE UNIQUE INDEX IF NOT EXISTS 'idx_configs_config_id' ON 'configs'('config_id');"
if [[ -v ADMIN ]]; then
sqlite3 /var/lib/databag/databag.db "delete from configs where config_id='configured';"
sqlite3 /var/lib/databag/databag.db "delete from configs where config_id='token';"
sqlite3 /var/lib/databag/databag.db "insert into configs (config_id, str_value) values ('token', '$ADMIN');"
sqlite3 /var/lib/databag/databag.db "insert into configs (config_id, bool_value) values ('configured', true);"
fi
if [ "$DEV" == "1" ]; then
cd /app/databag/net/server
./databag -p 7000 -w /app/databag/net/web/build -s /var/lib/databag -t /opt/databag/transform &
/app/dev_setup.sh || true
while true; do
sleep 1;
done
else
cd /app/databag/net/server
./databag -p 7000 -w /app/databag/net/web/build -s /var/lib/databag -t /opt/databag/transform
fi

9
net/repeater/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM golang:alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o repeater .
EXPOSE 7878
ENTRYPOINT ./repeater

View File

@ -0,0 +1,6 @@
FROM golang:alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN rm go.mod go.sum

70
net/repeater/go.mod Normal file
View File

@ -0,0 +1,70 @@
module repeater
go 1.17
require (
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.1
github.com/kr/pretty v0.3.1
github.com/stretchr/testify v1.8.4
github.com/theckman/go-securerandom v0.1.1
github.com/valyala/fastjson v1.6.4
golang.org/x/crypto v0.24.0
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.9
)
require (
cloud.google.com/go v0.112.1 // indirect
cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/firestore v1.15.0 // indirect
cloud.google.com/go/iam v1.1.7 // indirect
cloud.google.com/go/longrunning v0.5.5 // indirect
cloud.google.com/go/storage v1.40.0 // indirect
firebase.google.com/go/v4 v4.14.1 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/SherClockHolmes/webpush-go v1.3.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/otp v1.4.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/api v0.170.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/appengine/v2 v2.0.2 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

273
net/repeater/go.sum Normal file
View File

@ -0,0 +1,273 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw=
cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g=
firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g=
firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/SherClockHolmes/webpush-go v1.3.0 h1:CAu3FvEE9QS4drc3iKNgpBWFfGqNthKlZhp5QpYnu6k=
github.com/SherClockHolmes/webpush-go v1.3.0/go.mod h1:AxRHmJuYwKGG1PVgYzToik1lphQvDnqFYDqimHvwhIw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/theckman/go-securerandom v0.1.1 h1:5KctSyM0D5KKFK+bsypIyLq7yik0CEaI5i2fGcUGcsQ=
github.com/theckman/go-securerandom v0.1.1/go.mod h1:bmkysLfBH6i891sBpcP4xRM3XIB7jMeiKJB31jlResI=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48=
google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk=
google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc=
google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,61 @@
package repeater
import (
"encoding/json"
"log"
"net/http"
"os"
"runtime"
"strings"
"fmt"
"firebase.google.com/go/v4/messaging"
)
func ParseRequest(r *http.Request, w http.ResponseWriter, obj interface{}) error {
r.Body = http.MaxBytesReader(w, r.Body, 1024)
dec := json.NewDecoder(r.Body)
return dec.Decode(&obj)
}
func WriteResponse(w http.ResponseWriter, v interface{}) {
body, err := json.Marshal(v)
if err != nil {
_, file, line, _ := runtime.Caller(1)
p, _ := os.Getwd()
log.Printf("%s:%d %s", strings.TrimPrefix(file, p), line, err.Error())
w.WriteHeader(http.StatusInternalServerError)
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(body)
}
}
//Notify proxies push notification to device
func Notify(w http.ResponseWriter, r *http.Request) {
var msg PushMessage
if err := ParseRequest(r, w, &msg); err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
// See documentation on defining a message payload.
notification := &messaging.Notification{ Title: msg.Title, Body: msg.Body }
message := &messaging.Message{
Notification : notification,
Token: msg.Token,
}
// Send a message to the device corresponding to the provided
// registration token.
response, err := FCMClient.Send(FCMContext, message)
if err != nil {
ErrResponse(w, http.StatusBadRequest, err)
return
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
res := &PushResponse{ Message: response }
WriteResponse(w, res);
}

View File

@ -0,0 +1,60 @@
package repeater
import (
"github.com/kr/pretty"
"log"
"net/http"
"os"
"runtime"
"strings"
"time"
)
//Logger prints endpoint details
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}
//ErrResponse prints detailed error event and sets response
func ErrResponse(w http.ResponseWriter, code int, err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
p, _ := os.Getwd()
log.Printf("%s:%d %s", strings.TrimPrefix(file, p), line, err.Error())
}
w.WriteHeader(code)
}
//ErrMsg prints detailed error event
func ErrMsg(err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
p, _ := os.Getwd()
log.Printf("%s:%d %s", strings.TrimPrefix(file, p), line, err.Error())
}
}
//LogMsg prints detailed error string
func LogMsg(msg string) {
_, file, line, _ := runtime.Caller(1)
p, _ := os.Getwd()
log.Printf("%s:%d %s", strings.TrimPrefix(file, p), line, msg)
}
//PrintMsg prints debug message
func PrintMsg(obj interface{}) {
pretty.Println(obj)
}

View File

@ -0,0 +1,14 @@
package repeater
//PushMessage notification
type PushMessage struct {
Title string `json:"title"`
Body string `json:"body"`
Token string `json:"token"`
}
//PushResponse notification response
type PushResponse struct {
Message string `json:"message"`
}

View File

@ -0,0 +1,66 @@
package repeater
import (
"firebase.google.com/go/v4/messaging"
firebase "firebase.google.com/go/v4"
"github.com/gorilla/mux"
"net/http"
"strings"
"context"
"log"
)
type route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
type routes []route
var FCMClient *messaging.Client
var FCMContext context.Context
//NewRouter allocate router for databag API
func NewRouter() *mux.Router {
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
ctx := context.Background()
client, err := app.Messaging(ctx)
if err != nil {
log.Fatalf("error getting Messaging client: %v\n", err)
}
FCMClient = client
FCMContext = ctx
router := mux.NewRouter().StrictSlash(true)
for _, route := range endpoints {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
return router
}
var endpoints = routes{
route{
"Notify",
strings.ToUpper("Post"),
"/notify",
Notify,
},
}

40
net/repeater/main.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
app "repeater/internal"
"github.com/gorilla/handlers"
"log"
"net/http"
"os"
)
func main() {
var cert string
var key string
port := ":7878"
args := os.Args[1:];
for i := 0; i + 1 < len(args); i += 2 {
if args[i] == "-p" {
port = ":" + args[i + 1]
} else if args[i] == "-c" {
cert = args[i + 1]
} else if args[i] == "-k" {
key = args[i + 1]
}
}
router := app.NewRouter()
origins := handlers.AllowedOrigins([]string{"*"})
headers := handlers.AllowedHeaders([]string{"content-type", "authorization", "credentials"})
methods := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"})
if cert != "" && key != "" {
log.Printf("using args:" + " -p " + port[1:] + " -c " + cert + " -k " + key)
log.Fatal(http.ListenAndServeTLS(port, cert, key, handlers.CORS(origins, headers, methods)(router)))
} else {
log.Printf("using args:" + " -p " + port[1:])
log.Fatal(http.ListenAndServe(port, handlers.CORS(origins, headers, methods)(router)))
}
}

View File

@ -0,0 +1,17 @@
[Unit]
Description=push notification repeater for databag network
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=root
ExecStart=/usr/bin/repeater -p 443 -c /etc/letsencrypt/live/repeater.coredb.org/fullchain.pem -k /etc/letsencrypt/live/repeater.coredb.org/privkey.pem
[Service]
Environment="GOOGLE_APPLICATION_CREDENTIALS=/opt/databag/databag.json"
[Install]
WantedBy=multi-user.target

1
net/server/.dockerignore Normal file
View File

@ -0,0 +1 @@
databag

1
net/server/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/databag

15
net/server/Dockerfile.dev Normal file
View File

@ -0,0 +1,15 @@
FROM golang:alpine
RUN apk add build-base imagemagick sqlite ffmpeg curl
RUN mkdir -p /opt/databag
RUN mkdir -p /var/lib/databag
RUN mkdir -p /app/databag/net
RUN mkdir -p /tmp/databag-go-cache
WORKDIR /tmp/databag-go-cache
COPY go.mod go.sum ./
RUN go mod download
WORKDIR /
RUN rm -rf /tmp/databag-go-cache

24
net/server/entrypoint.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
set -e
sqlite3 /var/lib/databag/databag.db "VACUUM;"
sqlite3 /var/lib/databag/databag.db "CREATE TABLE IF NOT EXISTS 'configs' ('id' integer NOT NULL UNIQUE,'config_id' text NOT NULL,'str_value' text,'num_value' integer,'bool_value' numeric,'bin_value' blob,PRIMARY KEY ('id'));"
sqlite3 /var/lib/databag/databag.db "CREATE UNIQUE INDEX IF NOT EXISTS 'idx_configs_config_id' ON 'configs'('config_id');"
if [[ -n "$ADMIN" ]]; then
sqlite3 /var/lib/databag/databag.db "delete from configs where config_id='configured';"
sqlite3 /var/lib/databag/databag.db "delete from configs where config_id='token';"
sqlite3 /var/lib/databag/databag.db "insert into configs (config_id, str_value) values ('token', '$ADMIN');"
sqlite3 /var/lib/databag/databag.db "insert into configs (config_id, bool_value) values ('configured', true);"
fi
if [[ -z "$DATABAG_PORT" ]]; then
DATABAG_PORT=7000
fi
cd /app/databag/net/server
if [[ "$DEV" == "1" ]]; then
CGO_ENABLED=1 go run main.go -p $DATABAG_PORT -w /app/databag/net/web/build -s /var/lib/databag -t /opt/databag/transform
else
./databag -p $DATABAG_PORT -w /app/databag/net/web/build -s /var/lib/databag -t /opt/databag/transform
fi

View File

@ -34,7 +34,11 @@ func AddAccountApp(w http.ResponseWriter, r *http.Request) {
return;
}
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
algorithm := otp.AlgorithmSHA256;
if account.MFAAlgorithm == APPMFASHA1 {
algorithm = otp.AlgorithmSHA1
}
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: algorithm}
if valid, _ := totp.ValidateCustom(code, account.MFASecret, time.Now(), opts); !valid {
err := store.DB.Transaction(func(tx *gorm.DB) error {
if account.MFAFailedTime + APPMFAFailPeriod > curTime {

View File

@ -25,7 +25,7 @@ func AddAdminMFAuth(w http.ResponseWriter, r *http.Request) {
Issuer: APPMFAIssuer,
AccountName: "admin",
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA256,
Algorithm: otp.AlgorithmSHA1,
})
err = store.DB.Transaction(func(tx *gorm.DB) error {

View File

@ -24,7 +24,7 @@ func AddMultiFactorAuth(w http.ResponseWriter, r *http.Request) {
Issuer: APPMFAIssuer,
AccountName: account.Handle,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA256,
Algorithm: otp.AlgorithmSHA1,
})
err = store.DB.Transaction(func(tx *gorm.DB) error {

View File

@ -40,8 +40,13 @@ func SetAdminAccess(w http.ResponseWriter, r *http.Request) {
return;
}
secret := getStrConfigValue(CNFMFASecret, "");
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
secret := getStrConfigValue(CNFMFASecret, "")
algorithm := getStrConfigValue(CNFMFAAlgorithm, APPMFASHA256)
mfaAlgorithm := otp.AlgorithmSHA256
if algorithm == APPMFASHA1 {
mfaAlgorithm = otp.AlgorithmSHA1
}
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: mfaAlgorithm}
if valid, _ := totp.ValidateCustom(code, secret, time.Now(), opts); !valid {
err := store.DB.Transaction(func(tx *gorm.DB) error {
if failedTime + APPMFAFailPeriod > curTime {

View File

@ -38,39 +38,44 @@ func SetAdminMFAuth(w http.ResponseWriter, r *http.Request) {
return;
}
mfaAlgorithm := APPMFASHA1
secret := getStrConfigValue(CNFMFASecret, "");
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA1}
if valid, _ := totp.ValidateCustom(code, secret, time.Now(), opts); !valid {
err := store.DB.Transaction(func(tx *gorm.DB) error {
if failedTime + APPMFAFailPeriod > curTime {
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAFailedCount, NumValue: failedCount + 1}).Error; res != nil {
return res
}
} else {
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAFailedTime, NumValue: curTime}).Error; res != nil {
return res
}
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAFailedCount, NumValue: 1}).Error; res != nil {
return res
mfaAlgorithm = APPMFASHA256
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
if valid, _ := totp.ValidateCustom(code, secret, time.Now(), opts); !valid {
err := store.DB.Transaction(func(tx *gorm.DB) error {
if failedTime + APPMFAFailPeriod > curTime {
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAFailedCount, NumValue: failedCount + 1}).Error; res != nil {
return res
}
} else {
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAFailedTime, NumValue: curTime}).Error; res != nil {
return res
}
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAFailedCount, NumValue: 1}).Error; res != nil {
return res
}
}
return nil
})
if err != nil {
LogMsg("failed to increment fail count");
}
return nil
})
if err != nil {
LogMsg("failed to increment fail count");
}
ErrResponse(w, http.StatusUnauthorized, errors.New("invalid code"))
return
ErrResponse(w, http.StatusUnauthorized, errors.New("invalid code"))
return
}
}
err := store.DB.Transaction(func(tx *gorm.DB) error {
@ -81,6 +86,13 @@ func SetAdminMFAuth(w http.ResponseWriter, r *http.Request) {
}).Create(&store.Config{ConfigID: CNFMFAConfirmed, BoolValue: true}).Error; res != nil {
return res
}
// upsert mfa algorithm
if res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "config_id"}},
DoUpdates: clause.AssignmentColumns([]string{"num_value"}),
}).Create(&store.Config{ConfigID: CNFMFAAlgorithm, StrValue: mfaAlgorithm}).Error; res != nil {
return res
}
return nil
})
if err != nil {

View File

@ -35,32 +35,37 @@ func SetMultiFactorAuth(w http.ResponseWriter, r *http.Request) {
return;
}
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
mfaAlgorithm := APPMFASHA1
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA1}
if valid, _ := totp.ValidateCustom(code, account.MFASecret, time.Now(), opts); !valid {
err := store.DB.Transaction(func(tx *gorm.DB) error {
if account.MFAFailedTime + APPMFAFailPeriod > curTime {
account.MFAFailedCount += 1
if res := tx.Model(account).Update("mfa_failed_count", account.MFAFailedCount).Error; res != nil {
return res
}
} else {
account.MFAFailedTime = curTime
if res := tx.Model(account).Update("mfa_failed_time", account.MFAFailedTime).Error; res != nil {
return res
}
account.MFAFailedCount = 1
if res := tx.Model(account).Update("mfa_failed_count", account.MFAFailedCount).Error; res != nil {
return res
mfaAlgorithm = APPMFASHA256
opts := totp.ValidateOpts{Period: 30, Skew: 1, Digits: otp.DigitsSix, Algorithm: otp.AlgorithmSHA256}
if valid, _ := totp.ValidateCustom(code, account.MFASecret, time.Now(), opts); !valid {
err := store.DB.Transaction(func(tx *gorm.DB) error {
if account.MFAFailedTime + APPMFAFailPeriod > curTime {
account.MFAFailedCount += 1
if res := tx.Model(account).Update("mfa_failed_count", account.MFAFailedCount).Error; res != nil {
return res
}
} else {
account.MFAFailedTime = curTime
if res := tx.Model(account).Update("mfa_failed_time", account.MFAFailedTime).Error; res != nil {
return res
}
account.MFAFailedCount = 1
if res := tx.Model(account).Update("mfa_failed_count", account.MFAFailedCount).Error; res != nil {
return res
}
}
return nil
})
if err != nil {
LogMsg("failed to increment fail count");
}
return nil
})
if err != nil {
LogMsg("failed to increment fail count");
}
ErrResponse(w, http.StatusUnauthorized, errors.New("invalid code"))
return
ErrResponse(w, http.StatusUnauthorized, errors.New("invalid code"))
return
}
}
err = store.DB.Transaction(func(tx *gorm.DB) error {
@ -69,6 +74,11 @@ func SetMultiFactorAuth(w http.ResponseWriter, r *http.Request) {
ErrResponse(w, http.StatusInternalServerError, res)
return res
}
account.MFAAlgorithm = mfaAlgorithm;
if res := tx.Model(account).Update("mfa_algorithm", account.MFAAlgorithm).Error; res != nil {
ErrResponse(w, http.StatusInternalServerError, res)
return res
}
account.AccountRevision += 1;
if res := tx.Model(&account).Update("account_revision", account.AccountRevision).Error; res != nil {
return res

View File

@ -121,9 +121,8 @@ func SendPushEvent(account store.Account, event string) {
if pushToken == "" || pushToken == "null" {
continue;
}
url := "https://fcm.googleapis.com/fcm/send"
payload := Payload{ Title: messageTitle, Body: messageBody, Sound: "default" };
message := Message{ Notification: payload, To: pushToken };
url := "https://repeater.coredb.org/notify"
message := PushMessage{ Title: messageTitle, Body: messageBody, Token: pushToken };
body, err := json.Marshal(message)
if err != nil {
@ -136,7 +135,6 @@ func SendPushEvent(account store.Account, event string) {
continue
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", "key=AAAAkgDXt8c:APA91bEjH67QpUWU6uAfCIXLqm0kf6AdPNVICZPCcWbmgW9NGYIErAxMDTy4LEbe4ik93Ho4Z-AJNIhr6nXXKC9qKmyKkkYHJWAEVH47_FXBQV6rsoi9ZB_oiuV66XKKAy1V40GmvfaX")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {

View File

@ -156,6 +156,12 @@ const APPMFAFailPeriod = 300
//APPMFAFailCount limit of login failures in period
const APPMFAFailCount = 4
//APPMFASHA256 internal mfa algorithm sha256
const APPMFASHA256 = "sha256"
//APPMFASHA1 internal mfa alogirthm sha1
const APPMFASHA1 = "sha1"
//AppCardStatus compares cards status with string
func AppCardStatus(status string) bool {
if status == APPCardPending {

View File

@ -78,6 +78,9 @@ const CNFMFAEnabled = "mfa_enabled"
//CNFMFAConfirmed specified if mfa has been confirmed for admin
const CNFMFAConfirmed = "mfa_confirmed"
//CNFMFAAlgorirthm specifies internal mfa alogirhtm to use
const CNFMFAAlgorithm = "mfa_algorithm"
//CNFMFASecret specified the mfa secret
const CNFMFASecret = "mfa_secret"

View File

@ -619,3 +619,13 @@ type Ring struct {
IcePassword string `json:"icePassword"`
}
type PushMessage struct {
Title string `json:"title"`
Body string `json:"body"`
Token string `json:"token"`
}
type PushResponse struct {
Message string `json:"message"`
}

View File

@ -84,6 +84,7 @@ type Account struct {
MFAEnabled bool `gorm:"not null;default:false"`
MFAConfirmed bool `gorm:"not null;default:false"`
MFASecret string
MFAAlgorithm string
MFAFailedTime int64
MFAFailedCount uint
Forward string

1
net/web/.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules

2
net/web/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
build

7
net/web/Dockerfile.dev Normal file
View File

@ -0,0 +1,7 @@
FROM node:22-alpine
WORKDIR /app
RUN npm install --global chokidar-cli
ENV SHELL=/bin/sh

View File

@ -193,7 +193,7 @@ export const en = {
securedMessage: 'Sealed Message',
mfaTitle: 'Multi-Factor Authentication',
mfaSteps: 'Store the SHA256 secret and confirm the verification code',
mfaSteps: 'Store the secret and confirm the verification code',
mfaError: 'verification code error',
mfaDisabled: 'verification temporarily disabled',
mfaConfirm: 'Confirm',
@ -403,7 +403,7 @@ export const fr = {
sealedMessage: 'Message Sécurisé',
mfaTitle: 'Authentification Multi-Factor',
mfaSteps: 'Enregistrez le secret SHA256 et confirmez le code de vérification',
mfaSteps: 'Enregistrez le secret et confirmez le code de vérification',
mfaEnter: 'Entrez votre code de vérification',
mfaError: 'erreur de code de vérification',
mfaDisabled: 'vérification temporairement désactivée',
@ -612,7 +612,7 @@ export const sp = {
sealedMessage: 'Mensaje Seguro',
mfaTitle: 'Autenticación de Dos Factores',
mfaSteps: 'Guarde el secreto SHA256 y confirme el código de verificación',
mfaSteps: 'Guarde el secreto y confirme el código de verificación',
mfaEnter: 'Ingresa tu código de verificación',
mfaError: 'error de código de verificación',
mfaDisabled: 'verificación temporalmente deshabilitada',
@ -821,7 +821,7 @@ export const pt = {
sealedMessage: 'Mensagem Segura',
mfaTitle: 'Autenticação de Dois Fatores',
mfaSteps: 'Salve o segredo SHA256 e confirme o código de verificação',
mfaSteps: 'Salve o segredo e confirme o código de verificação',
mfaEnter: 'Digite seu código de verificação',
mfaError: 'erro de código de verificação',
mfaDisabled: 'verificação temporariamente desativada',
@ -1030,7 +1030,7 @@ export const de = {
sealedMessage: 'Gesicherte Nachricht',
mfaTitle: 'Zwei-Faktor-Authentifizierung',
mfaSteps: 'Speichern Sie das SHA256-Geheimnis und bestätigen Sie den Bestätigungscode',
mfaSteps: 'Speichern Sie das Geheimnis und bestätigen Sie den Bestätigungscode',
mfaEnter: 'Geben Sie Ihren Bestätigungs-Code ein',
mfaError: 'Verifizierungscodefehler',
mfaDisabled: 'Verifizierung vorübergehend deaktiviert',
@ -1239,7 +1239,7 @@ export const ru = {
sealedMessage: 'Защищенное Cообщение',
mfaTitle: 'Двухфакторная аутентификация',
mfaSteps: 'Сохраните секрет SHA256 и подтвердите код подтверждения',
mfaSteps: 'Сохраните секрет и подтвердите код подтверждения',
mfaEnter: 'Введите Ваш верификационный код',
mfaError: 'ошибка проверочного кода',
mfaDisabled: 'проверка временно отключена',
@ -1252,3 +1252,212 @@ export const ru = {
confirmDisable: 'Отключение двухфакторной аутентификации',
disablePrompt: 'Вы уверены, что хотите отключить двухфакторную аутентификацию?',
};
export const el = {
code: 'el',
settings: 'Ρυθμίσεις',
contacts: 'Επαφές',
logout: 'Αποσύνδεση',
confirmLogout: 'Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;',
contactsUpdated: 'Η κατάσταση των επαφών ενημερώθηκε',
disconnected: 'Αποσυνδεθήκατε από το διακομιστή',
allDevices: 'Αποσύνδεση από όλες τις συσκευές',
ok: 'Εντάξει',
cancel: 'Ακύρωση',
enableNotifications: 'Ειδοποιήσεις',
new: 'Νέο',
newMessage: 'Νέο Μήνυμα',
topics: 'Συζητήσεις',
unsetSealing: 'Κατάργηση Κλειδιού Σφράγισης',
newTopic: 'Νέα Συζήτηση',
noContacts: 'Δεν υπάρχουν Επαφές',
noTopics: 'Δεν υπάρχουν Συζητήσεις',
noConnected: 'Δεν υπάρχουν Συνδεδεμένες Επαφές',
subjectOptional: 'Θέμα (προαιρετικό)',
members: 'Μέλη',
sealedTopic: 'Σφραγισμένο Θέμα',
start: 'Έναρξη',
communication: 'Επικοινωνία για τον Αποκεντρωμένο Ιστό',
setupProfile: 'Ρυθμίστε το προφίλ σας',
connectPeople: 'Συνδεθείτε με άτομα',
startConversation: 'Ξεκινήστε μια συνομιλία',
default: 'Προεπιλογή',
dark: 'Σκούρο',
light: 'Φωτεινό',
operationFailed: 'Η λειτουργία απέτυχε',
tryAgain: 'Παρακαλώ προσπαθήστε ξανά.',
add: 'Προσθήκη',
save: 'Αποθήκευση',
forget: 'Ξέχασε το',
unlock: 'Ξεκλείδωμα',
profile: 'Προφίλ',
application: 'Εφαρμογή',
account: 'Λογαριασμός',
name: 'Όνομα',
node: 'Κόμβος',
location: 'Τοποθεσία',
description: 'Περιγραφή',
timeFormat: 'Μορφή Ώρας',
dateFormat: 'Μορφή Ημερομηνίας',
theme: 'Θέμα',
language: 'Γλώσσα',
timeUs: '12ωρο',
timeEu: '24ωρο',
dateUs: 'μμ/ηη',
dateEu: 'ηη/μμ',
registry: 'Ορατός λογαριασμός στο Μητρώο Διακομιστή',
sealedTopics: 'Σφραγισμένα Θέματα',
changeLogin: 'Αλλαγή Σύνδεσης',
selectImage: 'Επιλογή',
profileImage: 'Εικόνα Προφίλ',
profileDetails: 'Λεπτομέρειες Προφίλ',
enableSealed: 'Ενεργοποίηση Σφραγισμένων Συζητήσεων',
password: 'Κωδικός Πρόσβασης',
newPassword: 'Νέος Κωδικός Πρόσβασης',
confirmPassword: 'Επιβεβαίωση Κωδικού Πρόσβασης',
deleteKey: "Πληκτρολογήστε 'delete' για να αφαιρέσετε το κλειδί",
delete: 'διαγραφή',
remove: 'Διαγραφή',
username: 'Όνομα Χρήστη',
updateProfile: 'Ενημέρωση Προφίλ',
syncError: 'Σφάλμα Συγχρονισμού',
callTip: 'Κλήση Επαφής',
messageTip: 'Μήνυμα Επαφής',
connectedTip: 'Συνδεδεμένη Επαφή',
requestedTip: 'Αίτημα Σύνδεσης από Επαφή',
connectingTip: 'Αίτημα Σύνδεσης',
pendingTip: 'Αίτημα Σύνδεσης από Άγνωστη Επαφή',
confirmedTip: 'Αποσυνδεδεμένη Επαφή',
unsavedTip: 'Άγνωστη Επαφή',
actions: 'Ενέργειες',
resync: 'Εκ νέου συγχρονισμός',
connect: 'Σύνδεση',
disconnect: 'Αποσύνδεση',
disconnectContact: 'Αποσύνδεση Επαφής',
deleteContact: 'Διαγραφή Επαφής',
saveContact: 'Αποθήκευση Επαφής',
saveAccept: 'Αποθήκευση και Αποδοχή Σύνδεσης',
saveRequest: 'Αποθήκευση και Αίτημα Σύνδεσης',
ignoreRequest: 'Αγνόηση Αιτήματος',
acceptConnection: 'Αποδοχή Σύνδεσης',
requestConnection: 'Αίτημα Σύνδεσης',
cancelRequest: 'Ακύρωση Αιτήματος',
resyncContact: 'Εκ νέου συγχρονισμός Επαφής',
login: 'Σύνδεση',
create: 'Δημιουργία',
createAccount: 'Δημιουργία Λογαριασμού',
accountLogin: 'Σύνδεση Λογαριασμού',
toCreate: 'Οι λογαριασμοί δημιουργούνται μέσω συνδέσμου από τον πίνακα διαχειριστή.',
admin: 'Διαχειριστής',
loginError: 'Σφάλμα Σύνδεσης',
loginMessage: 'Παρακαλώ επιβεβαιώστε το όνομα χρήστη και τον κωδικό πρόσβασης.',
createError: 'Σφάλμα Δημιουργίας Λογαριασμού',
createMessage: 'Ελέγξτε το με τον διαχειριστή σας.',
adminError: 'Σφάλμα Πρόσβασης Διαχειριστή',
adminMessage: 'Παρακαλώ επιβεβαιώστε τον κωδικό πρόσβασης.',
confirmDelete: 'Διαγραφή Λογαριασμού',
areSure: 'Είστε σίγουροι ότι θέλετε να διαγράψετε το λογαριασμό;',
mb: 'MB',
gb: 'GB',
copied: 'Αντιγράφηκε',
accounts: 'Λογαριασμοί',
accessAccount: 'Πρόσβαση Λογαριασμού',
browserLink: 'Σύνδεσμος Περιηγητή',
mobileToken: 'Token Κινητού',
createLink: 'Δημιουργία Συνδέσμου Λογαριασμού',
configureServer: 'Διαμόρφωση Διακομιστή',
reloadAccounts: 'Επαναφόρτωση Λογαριασμών',
disableAccount: 'Απενεργοποίηση Λογαριασμού',
enableAccount: 'Ενεργοποίηση Λογαριασμού',
deleteAccount: 'Διαγραφή Λογαριασμού',
hostHint: 'domain:port/app',
federatedHost: 'Διεύθυνση Διακομιστή για Ομοσπονδιακή λειτουργία',
storageLimit: 'Όριο Αποθήκευσης (GB) / Λογαριασμό',
storageHint: '0 για Απεριόριστο',
keyType: 'Τύπος Κλειδιού Λογαριασμού',
accountCreation: 'Δυνατότητα δημιουργίας Δημόσιου Λογαριασμού',
enablePush: 'Ενεργοποίηση Ειδοποιήσεων',
allowUnsealed: 'Επιτρέψτε μη Σφραγισμένες Συζητήσεις',
topicContent: 'Περιεχόμενο Συζήτησης:',
enableImage: 'Ενεργοποίηση Ουράς Εικόνων',
imageHint: 'Επιτρέψτε την ανάρτηση εικόνων σε συζητήσεις',
enableAudio: 'Ενεργοποίηση Ουράς Ήχου',
audioHint: 'Επιτρέψτε την ανάρτηση ήχου σε συζητήσεις',
enableVideo: 'Ενεργοποίηση Ουράς Βίντεο',
videoHint: 'Επιτρέψτε την ανάρτηση βίντεο σε συζητήσεις',
enableBinary: 'Ενεργοποίηση Ψηφιακών Αρχείων',
binaryHint: 'Επιτρέψτε την ανάρτηση υπολοίπων ψηφιακών αρχείων σε συζητήσεις',
enableWeb: 'Ενεργοποίηση Κλήσεων WebRTC',
webHint: 'Ενεργοποίηση κλήσεων ήχου και βίντεο για τις επαφές',
enableService: 'Υπηρεσία Cloudflare',
serviceHint: 'Ενεργοποίηση Υπηρεσίας Cloudflare',
serverUrl: 'URL Διακομιστή WebRTC',
urlHint: 'turn:ip:port?transport=udp',
webUsername: 'Όνομα Χρήστη WebRTC',
webPassword: 'Κωδικός Πρόσβασης WebRTC',
failedLoad: 'Αποτυχία Φόρτωσης',
limit: 'Όριο',
deleteMessage: 'Διαγραφή Μηνύματος',
messageHint: 'Είστε σίγουροι ότι θέλετε να διαγράψετε το μήνυμα;',
attachImage: 'Επισύναψη Εικόνας',
attachVideo: 'Επισύναψη Βίντεο',
attachAudio: 'Επισύναψη Ήχου',
attachFile: 'Επισύναψη Αρχείου',
fontColor: 'Αλλαγή Χρώματος Γραμματοσειράς',
fontSize: 'Αλλαγή Μεγέθους Γραμματοσειράς',
postMessage: 'Ανάρτηση Μηνύματος',
close: 'Κλείσιμο',
leave: 'Αποχώρηση',
confirmTopic: 'Διαγραφή συζήτησης',
sureTopic: 'Είστε σίγουροι ότι θέλετε να διαγράψετε αυτήν τη συζήτηση;',
confirmLeave: 'Αποχώρηση από τη συζήτηση',
sureLeave: 'Είστε σίγουροι ότι θέλετε να αποχωρήσετε από αυτήν τη συζήτηση;',
details: 'Λεπτομέρειες',
host: 'Διακομιστής',
guest: 'Επισκέπτης',
editSubject: 'Επεξεργασία Θέματος',
editMembership: 'Επεξεργασία Μελών',
deleteTopic: 'Διαγραφή Συζήτησης',
leaveTopic: 'Αποχώρηση από τη συζήτηση',
integrated: 'Ενσωματωμένο',
microphone: 'Μικρόφωνο',
camera: 'Κάμερα',
notes: 'Σημειώσεις',
disconnecting: 'Αποσύνδεση Επαφής',
confirmDisconnect: 'Είστε σίγουροι ότι θέλετε να αποσυνδέσετε την επαφή;',
removing: 'Διαγραφή Επαφής',
confirmRemove: 'Είστε σίγουροι ότι θέλετε να διαγράψετε την επαφή;',
message: 'Μήνυμα',
securedMessage: 'Κρυπτογραφημένο Μήνυμα',
mfaTitle: 'Επαλήθευση Πολλαπλών Παραγόντων (MFA)',
mfaSteps: 'Αποθηκεύστε το μυστικό και επιβεβαιώστε τον κωδικό επαλήθευσης',
mfaError: 'σφάλμα κωδικού επαλήθευσης',
mfaDisabled: 'η επαλήθευση είναι προσωρινά απενεργοποιημένη',
mfaConfirm: 'Επιβεβαίωση',
mfaEnter: 'Εισάγετε τον κωδικό επαλήθευσης σας',
enableMultifactor: 'Ενεργοποίηση επαλήθευσης πολλαπλών παραγόντων (MFA)',
disableMultifactor: 'Απενεργοποίηση επαλήθευσης πολλαπλών παραγόντων (MFA)',
disable: 'Απενεργοποίηση',
confirmDisable: 'Απενεργοποίηση Επαλήθευσης Πολλαπλών Παραγόντων (MFA)',
disablePrompt: 'Είστε σίγουροι πως θέλετε να απενεργοποιήσετε την επαλήθευση πολλαπλών παραγόντων (MFA)',
};

View File

@ -126,7 +126,7 @@ export function TopicItem({ host, contentKey, sealed, topic, update, remove, str
)}
{ !sealed && !state.editing && (
<div className="message">
<div style={{ color: topic.textColor ? topic.textColor : colors.mainText, fontSize: topic.textSize }}>{ topic.clickable }</div>
<div style={{ color: topic.textColor ? topic.textColor : colors.mainText, fontSize: topic.textSize, whiteSpace: 'pre-wrap' }}>{ topic.clickable }</div>
</div>
)}
{ state.editing && (

View File

@ -138,18 +138,15 @@ export function useConversation(cardId, channelId) {
}, [state.contentKey]);
const clickableText = (text) => {
const urlPattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
const urlPattern = new RegExp('(https?:\\/\\/)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)');
const hostPattern = new RegExp('^https?:\\/\\/', 'i');
let group = '';
let clickable = [];
const words = text === [] ? '' : DOMPurify.sanitize(text).split(' ');
words.forEach((word, index) => {
if (!!urlPattern.test(word)) {
clickable.push(<span key={index}>{ group }</span>);
@ -161,6 +158,7 @@ export function useConversation(cardId, channelId) {
group += `${word} `;
}
})
clickable.push(<span key={words.length}>{ group }</span>);
return <p>{ clickable }</p>;
};