From 4755177289c14621257398c779df463c9f24631c Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 20 Feb 2025 00:51:26 +0100 Subject: [PATCH 001/239] remove old stuff --- scripts/create_invite.sh | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 scripts/create_invite.sh diff --git a/scripts/create_invite.sh b/scripts/create_invite.sh deleted file mode 100644 index e33489e..0000000 --- a/scripts/create_invite.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Load the .env file from the parent directory -source "$(dirname "$0")/../.env" - -# Generate a random UUID for the record -UUID=$(uuidgen) - -# Create a fake inviter_npub (using a random string here) -INVITER_PUB_KEY="fake_inviter_${RANDOM}" - -# Get the current timestamp for created_at -CREATED_AT=$(date +"%Y-%m-%d %H:%M:%S") - -# Get the name of the PostgreSQL container from the docker-compose configuration -POSTGRES_CONTAINER_NAME=$(docker compose ps -q postgres | xargs docker inspect --format '{{.Name}}' | sed 's/\///') - -# Connect to the Postgres container and insert the fake record into the app_invite table -docker exec -i "$POSTGRES_CONTAINER_NAME" psql -v -U "$POSTGRES_USER" -d "$POSTGRES_DB" -h localhost -p 5432 -c " - INSERT INTO app_invite_created (uuid, inviter_npub, created_at) - VALUES ('$UUID', '$INVITER_PUB_KEY', '$CREATED_AT') - RETURNING *; -" - -# Print the generated values -echo "Inserted app_invite_created Record:" -echo "UUID: $UUID" -echo "Inviter Npub: $INVITER_PUB_KEY" -echo "Created At: $CREATED_AT" -echo "Try it at http://localhost/invite/$UUID" From 56c31cb4b5ed9eb5ff064982032c4df2a340e181 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 20 Feb 2025 00:51:30 +0100 Subject: [PATCH 002/239] formatting --- src/public/css/seca.css | 1 - src/views/invite.ejs | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 47b0801..7df0d5e 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -71,7 +71,6 @@ h1 { cursor: default; } - #nostr-signup-button p { font-size: 1.5rem; } diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 5441e84..519bb83 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -20,7 +20,11 @@
From 39032dcd7bb2470601507d9d4cb286c920d74166 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 20 Feb 2025 14:53:09 +0100 Subject: [PATCH 003/239] visuals in place --- src/public/css/seca.css | 72 ++++++++++-- src/public/img/envelope.svg | 40 +++++++ src/public/img/lasecagold_ostrich.svg | 40 +++++++ src/public/img/message-exclamation.svg | 6 + src/public/img/phone.svg | 40 +++++++ src/public/img/signal-messenger.svg | 40 +++++++ src/public/img/square-whatsapp.svg | 40 +++++++ src/public/img/telegram.svg | 40 +++++++ src/public/img/white_ostrich.svg | 40 ++++++- src/views/createProfile.ejs | 153 +++++++++++++++---------- src/views/invite.ejs | 10 +- 11 files changed, 443 insertions(+), 78 deletions(-) create mode 100644 src/public/img/envelope.svg create mode 100644 src/public/img/lasecagold_ostrich.svg create mode 100644 src/public/img/message-exclamation.svg create mode 100644 src/public/img/phone.svg create mode 100644 src/public/img/signal-messenger.svg create mode 100644 src/public/img/square-whatsapp.svg create mode 100644 src/public/img/telegram.svg diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 7df0d5e..9c9fddd 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -3,11 +3,17 @@ box-sizing: border-box; } +.badges { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(3, 1fr); +} + .badge { - border: 2px solid black; + border: 2px solid #e1c300; border-radius: 10px; - margin: 5px; - padding: 5px; + padding: 2%; + margin: 2%; } body { @@ -18,12 +24,59 @@ h1 { color: #e1c300; } +.over-background { + background-color: white; + border-radius: 1vw; + padding: 2vw; + margin: 1%; +} + +#create-profile-card-content > * { + text-align: center; + margin-right: auto; + margin-left: auto; + display: block; + max-width: 1000px; +} + +.create-profile-card-section > * { + text-align: center; + margin-right: auto; + margin-left: auto; + display: block; + max-width: 1000px; +} + +.create-profile-card-section .badge img { + width: 10%; +} + +.button-primary { + background: #e1c300; + border-radius: 1vw; + border: 0; + color: white; + padding: 1%; + cursor: pointer; +} + +.button-primary:hover { + background-color: #dfca41; +} + +.button-primary:disabled { + background-color: #d8d8d8; + cursor: default; +} + +.button-large { + font-weight: bold; + font-size: 1.5em; +} + .invite-card { margin: 5vh 20vw; - background-color: white; height: 100%; - padding: 2vw; - border-radius: 1vw; } .invite-card-content { @@ -36,18 +89,18 @@ h1 { text-align: center; } -.invite-card-popup { +.card-secondary { background-color: #369; border-radius: 1vw; padding: 1vw; color: white; } -.invite-card-popup a { +.card-secondarya { color: white; } -.invite-card-popup a:hover { +.card-secondarya:hover { color: #e1c300; } @@ -57,7 +110,6 @@ h1 { border-radius: 1vw; border: 0; color: white; - font-weight: bold; padding: 1%; cursor: pointer; } diff --git a/src/public/img/envelope.svg b/src/public/img/envelope.svg new file mode 100644 index 0000000..879c222 --- /dev/null +++ b/src/public/img/envelope.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/img/lasecagold_ostrich.svg b/src/public/img/lasecagold_ostrich.svg new file mode 100644 index 0000000..16881c0 --- /dev/null +++ b/src/public/img/lasecagold_ostrich.svg @@ -0,0 +1,40 @@ + + + + + + diff --git a/src/public/img/message-exclamation.svg b/src/public/img/message-exclamation.svg new file mode 100644 index 0000000..52d4cb8 --- /dev/null +++ b/src/public/img/message-exclamation.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/public/img/phone.svg b/src/public/img/phone.svg new file mode 100644 index 0000000..94dae70 --- /dev/null +++ b/src/public/img/phone.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/img/signal-messenger.svg b/src/public/img/signal-messenger.svg new file mode 100644 index 0000000..442edc3 --- /dev/null +++ b/src/public/img/signal-messenger.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/img/square-whatsapp.svg b/src/public/img/square-whatsapp.svg new file mode 100644 index 0000000..08fc447 --- /dev/null +++ b/src/public/img/square-whatsapp.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/img/telegram.svg b/src/public/img/telegram.svg new file mode 100644 index 0000000..fca7d4d --- /dev/null +++ b/src/public/img/telegram.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/img/white_ostrich.svg b/src/public/img/white_ostrich.svg index a06e4d7..ba04054 100644 --- a/src/public/img/white_ostrich.svg +++ b/src/public/img/white_ostrich.svg @@ -1,3 +1,39 @@ - - + + + + + diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 25be8bb..2e75bf2 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -9,69 +9,100 @@ -

Crea tu perfil

-

Tu clave de Nostr ya es parte de la seca.

-

Añade detalles a tu perfil para poder empezar a comerciar.

-
- - -
-

Añade métodos de contacto para poder hablar con otros miembros.

-
-
- 📱 Teléfono -
-
- 📱 WhatsApp -
-
- 📩 Telegram -
-
- 📧 Email -
-
- 📧 Nostr -
-
- 📧 Signal -
-
- 📧 Matrix -
-
- 📧 XMPP -
-
- 📧 Simplex -
+
+
+
+

Crea tu perfil

+

Tu clave de Nostr ya es parte de la seca.

+

Añade detalles a tu perfil para poder empezar a comerciar.

-
-

Contactos añadidos

-
+
+
+

Perfil

+ +
- - +
+
+

Datos de contacto

+
+

+ Añade métodos de contacto para poder hablar con otros miembros. + Debes añadir al menos uno. Siempre puedes editarlos más adelante. +

+ + + +
+

+ Tus métodos de contacto son necesarios para hablar con otros + miembros y poder comerciar. +

+

+ Tus métodos de contacto se guardan encriptados con tu clave de + nostr. La seca no puede verlos. +

+

Solo los miembros con los que tú decidas podrán verlos.

+
+ +
+
+ + +

Teléfono

+ +
+
+ +

Whatsapp

+ +
+
+ +

Telegram

+ +
+
+ +

Email

+ +
+
+ +

Nostr

+ +
+
+ +

Signal

+ +
+
+
+ +
+
+
diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 519bb83..03d8a4d 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -10,7 +10,7 @@
-
+

¡Has sido invitado a la seca!

@@ -18,7 +18,7 @@

Usa tu extensión de Nostr para darte de alta:

-
@@ -106,4 +111,5 @@
+ From 3154a8511a5cef3b8f890222cef42b937628bb2c Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 20 Feb 2025 20:01:50 +0100 Subject: [PATCH 007/239] create profile --- src/public/javascript/createProfile.js | 42 +++++++++++++++++++++--- src/views/createProfile.ejs | 45 ++++++++++++++++++-------- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/public/javascript/createProfile.js b/src/public/javascript/createProfile.js index 0b0c334..80e9fb0 100644 --- a/src/public/javascript/createProfile.js +++ b/src/public/javascript/createProfile.js @@ -129,7 +129,7 @@ function debounce(func, wait) { }; } -validateNymInput = debounce(() => { +const validateNymInput = debounce(() => { const nymValue = nymInput.value.trim(); const isValid = nymValue.length >= 3 && nymValue.length <= 128; if (isValid) { @@ -139,7 +139,7 @@ validateNymInput = debounce(() => { } }, 500); -checkIfSubmittable = debounce((allInputs) => { +const checkIfSubmittable = debounce((allInputs) => { const nymIsFilled = allInputs.nymInput.value !== ''; let atLeastOneContactIsFilled = false; @@ -153,7 +153,37 @@ checkIfSubmittable = debounce((allInputs) => { submitProfileButton.disabled = buttonShouldBeDisabled; }, 1000); -function onLoadErrands(allInputs) { +async function createProfile(allInputs) { + const contactDetails = []; + for (const someInput of allInputs.contactInputs) { + contactDetails.push({ + type: someInput.getAttribute('data-type'), + value: someInput.value, + }); + } + const encryptedContactDetails = await window.nostr.nip04.encrypt( + await window.nostr.getPublicKey(), + JSON.stringify(contactDetails) + ); + await fetch('/api/set-contact-details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ encryptedContactDetails }), + }); + + const nym = allInputs.nymInput.value; + await fetch('/api/set-nym', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ nym }), + }); +} + +function onLoadErrands(allInputs, submitProfileButton) { allInputs.nymInput.addEventListener('input', validateNymInput); for (const someInput of allInputs.allInputs) { @@ -163,6 +193,10 @@ function onLoadErrands(allInputs) { } checkIfSubmittable(allInputs); + + submitProfileButton.addEventListener('click', () => { + createProfile(allInputs); + }); } const nymInput = document.getElementById('nym-input'); @@ -198,4 +232,4 @@ const allInputs = { ], }; -onLoadErrands(allInputs); +onLoadErrands(allInputs, submitProfileButton); diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 6ab04f1..606d9ec 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -67,36 +67,55 @@
-
+
-

Teléfono

- +
-
+

Whatsapp

- +
-
+

Telegram

- +
-
+

Email

- +
-
+

Nostr

- +
-
+

Signal

- +
From c38feef86fdeb76860c4d7280d9904e03b5ddc40 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 20 Feb 2025 23:13:33 +0100 Subject: [PATCH 008/239] redirect --- src/public/javascript/createProfile.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/public/javascript/createProfile.js b/src/public/javascript/createProfile.js index 80e9fb0..bfb9ac5 100644 --- a/src/public/javascript/createProfile.js +++ b/src/public/javascript/createProfile.js @@ -151,7 +151,7 @@ const checkIfSubmittable = debounce((allInputs) => { const buttonShouldBeDisabled = !(nymIsFilled && atLeastOneContactIsFilled); submitProfileButton.disabled = buttonShouldBeDisabled; -}, 1000); +}, 500); async function createProfile(allInputs) { const contactDetails = []; @@ -181,6 +181,10 @@ async function createProfile(allInputs) { }, body: JSON.stringify({ nym }), }); + + setTimeout(() => { + window.location.href = '/home'; + }, 1000); } function onLoadErrands(allInputs, submitProfileButton) { From f57a20a3a394cb0b2204258be427a43d1003fa02 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 20 Feb 2025 23:45:13 +0100 Subject: [PATCH 009/239] don't allow going home unless profile details are complete --- ...directIfMissingProfileDetailsMiddleware.js | 12 +++++++++ src/routes/webRoutes.js | 25 ++----------------- src/services/profileService.js | 12 +++++++++ 3 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 src/middlewares/redirectIfMissingProfileDetailsMiddleware.js diff --git a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js new file mode 100644 index 0000000..934048d --- /dev/null +++ b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js @@ -0,0 +1,12 @@ +const profileService = require('../services/profileService'); + +async function redirectIfMissingProfileDetailsMiddleware(req, res, next) { + const publicKey = req.cookies.publicKey; + if (!(await profileService.areProfileDetailsComplete(publicKey))) { + res.redirect('/createProfile'); + } + + next(); +} + +module.exports = redirectIfMissingProfileDetailsMiddleware; diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 070dbfe..911995e 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -4,6 +4,7 @@ const router = express.Router(); const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotAuthorizedMiddleware'); const invitesService = require('../services/invitesService'); const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); +const redirectIfMissingProfileDetailsMiddleware = require('../middlewares/redirectIfMissingProfileDetailsMiddleware'); router.get('/', (req, res) => { res.render('index', { uuid: req.cookies.sessionUuid }); @@ -32,29 +33,6 @@ router.get('/invite/:inviteUuid', async (req, res) => { return res.render('invite', { invite }); }); -router.get('/invite/:inviteUuid', async (req, res) => { - const { inviteUuid } = req.params; - - res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); - - let invite; - try { - invite = await invitesService.getAppInvite(inviteUuid); - if (!invite) { - return res.status(404).render('error', { message: 'Invite not found.' }); - } - - if (await invitesService.isAppInviteSpent(inviteUuid)) { - return res.status(410).render('invite_spent', { invite }); - } - } catch (error) { - console.error('Error fetching invite:', error); - return res.status(500).render('error', { message: 'An error occurred' }); - } - - return res.render('invite', { invite }); -}); - router.get( '/createProfile', redirectIfNotAuthorizedMiddleware, @@ -67,6 +45,7 @@ router.get( '/home', redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, (req, res) => { res.render('home', {}); } diff --git a/src/services/profileService.js b/src/services/profileService.js index 7e6b4a1..5db0c24 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -20,7 +20,19 @@ async function setNym(publicKey, nym) { }); } +async function areProfileDetailsComplete(publicKey) { + const isNymSet = await NymSet.findOne({ where: { public_key: publicKey } }); + const areContactDetailsSet = await ContactDetailsSet.findOne({ + where: { + public_key: publicKey, + }, + }); + + return isNymSet && areContactDetailsSet; +} + module.exports = { setContactDetails, setNym, + areProfileDetailsComplete, }; From 2e79a1faddbb2630a373b6a9ba1ce4c7783e12ea Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 15:58:57 +0100 Subject: [PATCH 010/239] redirect now working properly --- src/middlewares/redirectIfNotAuthorizedMiddleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewares/redirectIfNotAuthorizedMiddleware.js b/src/middlewares/redirectIfNotAuthorizedMiddleware.js index fb2899e..93fbc32 100644 --- a/src/middlewares/redirectIfNotAuthorizedMiddleware.js +++ b/src/middlewares/redirectIfNotAuthorizedMiddleware.js @@ -2,7 +2,7 @@ const sessionService = require('../services/sessionService'); async function redirectIfNotAuthorizedMiddleware(req, res, next) { if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { - res.redirect('/'); + return res.redirect('/'); } next(); } From 70f9118d82aed3ad42955eb772da148aa159e897 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 16:07:29 +0100 Subject: [PATCH 011/239] redirect nicely depending on authorized or not --- src/middlewares/redirectHomeIfAuthorized.js | 10 ++++++++++ .../redirectIfNotAuthorizedMiddleware.js | 2 +- src/routes/webRoutes.js | 14 ++++++++++++-- src/views/{index.ejs => login.ejs} | 0 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/middlewares/redirectHomeIfAuthorized.js rename src/views/{index.ejs => login.ejs} (100%) diff --git a/src/middlewares/redirectHomeIfAuthorized.js b/src/middlewares/redirectHomeIfAuthorized.js new file mode 100644 index 0000000..9619c68 --- /dev/null +++ b/src/middlewares/redirectHomeIfAuthorized.js @@ -0,0 +1,10 @@ +const sessionService = require('../services/sessionService'); + +async function redirectHomeIfAuthorized(req, res, next) { + if (await sessionService.isSessionAuthorized(req.cookies.sessionUuid)) { + return res.redirect('/home'); + } + next(); +} + +module.exports = redirectHomeIfAuthorized; diff --git a/src/middlewares/redirectIfNotAuthorizedMiddleware.js b/src/middlewares/redirectIfNotAuthorizedMiddleware.js index 93fbc32..f4b9e47 100644 --- a/src/middlewares/redirectIfNotAuthorizedMiddleware.js +++ b/src/middlewares/redirectIfNotAuthorizedMiddleware.js @@ -2,7 +2,7 @@ const sessionService = require('../services/sessionService'); async function redirectIfNotAuthorizedMiddleware(req, res, next) { if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { - return res.redirect('/'); + return res.redirect('/login'); } next(); } diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 911995e..0e0b60d 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -5,9 +5,19 @@ const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotA const invitesService = require('../services/invitesService'); const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); const redirectIfMissingProfileDetailsMiddleware = require('../middlewares/redirectIfMissingProfileDetailsMiddleware'); +const redirectHomeIfAuthorized = require('../middlewares/redirectHomeIfAuthorized'); -router.get('/', (req, res) => { - res.render('index', { uuid: req.cookies.sessionUuid }); +router.get( + '/', + redirectHomeIfAuthorized, + redirectIfNotAuthorizedMiddleware, + (req, res) => { + res.redirect('/login'); + } +); + +router.get('/login', redirectHomeIfAuthorized, (req, res) => { + res.render('login', { uuid: req.cookies.sessionUuid }); }); router.get('/invite/:inviteUuid', async (req, res) => { diff --git a/src/views/index.ejs b/src/views/login.ejs similarity index 100% rename from src/views/index.ejs rename to src/views/login.ejs From 2e37709677bea47ff0e70ed2bc7affa03455b72b Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 16:26:26 +0100 Subject: [PATCH 012/239] remove outdated css --- src/public/css/seca.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index cf4dd42..ff05fa7 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -31,14 +31,6 @@ h1 { margin: 1%; } -#create-profile-card-content > * { - text-align: center; - margin-right: auto; - margin-left: auto; - display: block; - max-width: 1000px; -} - .create-profile-card-section > * { text-align: center; margin-right: auto; From 414d31a3cc4cbd558f869a7acc36cb19598fc290 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 16:41:22 +0100 Subject: [PATCH 013/239] style the login page --- src/public/css/seca.css | 25 ++++++-- src/public/javascript/{index.js => login.js} | 0 src/views/createProfile.ejs | 6 +- src/views/invite.ejs | 2 +- src/views/login.ejs | 66 +++++++++++--------- 5 files changed, 59 insertions(+), 40 deletions(-) rename src/public/javascript/{index.js => login.js} (100%) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index ff05fa7..80b5f46 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -31,6 +31,14 @@ h1 { margin: 1%; } +#login-card > * { + text-align: center; + margin-right: auto; + margin-left: auto; + display: block; + margin-bottom: 1vh; +} + .create-profile-card-section > * { text-align: center; margin-right: auto; @@ -93,7 +101,7 @@ h1 { color: white; } -.card-secondarya { +.card-secondary a { color: white; } @@ -101,25 +109,30 @@ h1 { color: #e1c300; } -#nostr-signup-button { - width: 20vw; +.button-nostr { background: #a915ff; border-radius: 1vw; - border: 0; color: white; padding: 1%; cursor: pointer; } -#nostr-signup-button:hover { +.button-nostr:hover { background-color: #b941ff; } -#nostr-signup-button:disabled { +.button-nostr:disabled { background-color: #d8d8d8; cursor: default; + border: 0; } +#nostr-signup-button { + width: 20vw; + border: 0; +} + + #nostr-signup-button p { font-size: 1.5rem; } diff --git a/src/public/javascript/index.js b/src/public/javascript/login.js similarity index 100% rename from src/public/javascript/index.js rename to src/public/javascript/login.js diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 606d9ec..87b57ef 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -10,14 +10,14 @@
-
+

Crea tu perfil

Tu clave de Nostr ya es parte de la seca.

Añade detalles a tu perfil para poder empezar a comerciar.

-
+

Perfil

-
+

Datos de contacto

diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 03d8a4d..fee903e 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -18,7 +18,7 @@

Usa tu extensión de Nostr para darte de alta:

- - +

+
+
+ From a3ca2636e1b97f56234358ca921a8306d747d1ba Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 16:49:27 +0100 Subject: [PATCH 014/239] start to make home nice --- src/public/css/seca.css | 1 - src/views/home.ejs | 16 ++++- src/views/invite.ejs | 6 +- src/views/login.ejs | 154 ++++++++++++++++++++-------------------- 4 files changed, 96 insertions(+), 81 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 80b5f46..4cebba8 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -132,7 +132,6 @@ h1 { border: 0; } - #nostr-signup-button p { font-size: 1.5rem; } diff --git a/src/views/home.ejs b/src/views/home.ejs index b3e3b98..ecb8241 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -4,10 +4,22 @@ Seca home + -

seca

-

Bienvenido a la seca

+
+

la seca

+

Bienvenido a la seca

+
+
+ + + +
+
+

Compraventas activas

+

Vaya, parece que no hay nada...

+
diff --git a/src/views/invite.ejs b/src/views/invite.ejs index fee903e..0cd5df1 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -18,7 +18,11 @@

Usa tu extensión de Nostr para darte de alta:

- -
- - - - +

+

+ nos2x-fox +

+

Chrome

+

+ Alby +

+

+ nos2x +

+
+
From 39f6fe49d1672adbb11a378925d36894ab712235 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 16:56:58 +0100 Subject: [PATCH 015/239] update readme --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9b9208e..5304cfb 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ -# Express hello world +# laseca -A joke application. +laseca is a social bitcoin to cash exchange, implemented as a webapp. ## How to set up dev environment * Pre-requisites + `docker` and `docker compose` + `node` and `npm` -* Run `npm install` -* You can now start the app in a container by running `npm run start:containers` (and shut it down with `npm run stop:containers`). - -## How to test - -* Run `npm run test` +* Installing + + Run `npm install` + + You can now start the app in a container by running `npm run start:containers` (and shut it down with `npm run stop:containers`). +* Running + + The app will run in a single container, with a Postgres database, a caddy webserver and the nodejs app. + + Note that the container doesn't come with a volume for Postgres: default behaviour is to start from scratch every time you create the container, delete everything every time you delete the container. + + The docker image launches the nodejs app with nodemon, so changes to the code will be available immediately. ## How to deploy From 72aeed411ae00fa84d222a5aa64965d1801aebe6 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 16:58:00 +0100 Subject: [PATCH 016/239] small refactor --- README.md | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5304cfb..757e378 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ laseca is a social bitcoin to cash exchange, implemented as a webapp. + `node` and `npm` * Installing + Run `npm install` - + You can now start the app in a container by running `npm run start:containers` (and shut it down with `npm run stop:containers`). + + You can now start the app in a container by running `npm run start:container` (and shut it down with `npm run stop:container`). * Running + The app will run in a single container, with a Postgres database, a caddy webserver and the nodejs app. + Note that the container doesn't come with a volume for Postgres: default behaviour is to start from scratch every time you create the container, delete everything every time you delete the container. diff --git a/package.json b/package.json index 3c1c48d..9f252f7 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "main": "src/app.js", "scripts": { "start": "node src/app.js", - "start:containers": "docker compose up -d --build", - "stop:containers": "docker compose down", + "start:container": "docker compose up -d --build", + "stop:container": "docker compose down", "cli": "node src/cli.js", "test": "playwright test", "lint": "eslint . --fix", From 66a0ab9bfe73b6937dd6a0da0c03d0be78712a2c Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 17:18:13 +0100 Subject: [PATCH 017/239] rename folder --- other/{Nostr Icons & Buttons => nostr-brand-assets}/About.svg | 0 .../App Icons & Favicons.svg | 0 .../Buttons and States.svg | 0 .../Nostr Brackets-1.svg | 0 .../Nostr Brackets-2.svg | 0 .../Nostr Brackets.svg | 0 .../Nostrich Head-1.svg | 0 .../Nostrich Head-2.svg | 0 .../Nostrich Head-3.svg | 0 .../Nostrich Head-4.svg | 0 .../Nostrich Head-5.svg | 0 .../Nostrich Head-6.svg | 0 .../Nostrich Head-7.svg | 0 .../Nostrich Head.svg | 0 .../{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich-1.svg | 0 .../{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich-2.svg | 0 .../{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich-3.svg | 0 other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich.svg | 0 .../{Nostr Icons & Buttons => nostr-brand-assets}/Original-1.svg | 0 .../{Nostr Icons & Buttons => nostr-brand-assets}/Original-2.svg | 0 .../{Nostr Icons & Buttons => nostr-brand-assets}/Original-3.svg | 0 other/{Nostr Icons & Buttons => nostr-brand-assets}/Original.svg | 0 .../Production-Ready Icons.svg | 0 other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka-1.svg | 0 other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka-2.svg | 0 other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka-3.svg | 0 other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka.svg | 0 27 files changed, 0 insertions(+), 0 deletions(-) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/About.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/App Icons & Favicons.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Buttons and States.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostr Brackets-1.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostr Brackets-2.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostr Brackets.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-1.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-2.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-3.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-4.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-5.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-6.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head-7.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich Head.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich-1.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich-2.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich-3.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Nostrich.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Original-1.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Original-2.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Original-3.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Original.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Production-Ready Icons.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka-1.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka-2.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka-3.svg (100%) rename other/{Nostr Icons & Buttons => nostr-brand-assets}/Shaka.svg (100%) diff --git a/other/Nostr Icons & Buttons/About.svg b/other/nostr-brand-assets/About.svg similarity index 100% rename from other/Nostr Icons & Buttons/About.svg rename to other/nostr-brand-assets/About.svg diff --git a/other/Nostr Icons & Buttons/App Icons & Favicons.svg b/other/nostr-brand-assets/App Icons & Favicons.svg similarity index 100% rename from other/Nostr Icons & Buttons/App Icons & Favicons.svg rename to other/nostr-brand-assets/App Icons & Favicons.svg diff --git a/other/Nostr Icons & Buttons/Buttons and States.svg b/other/nostr-brand-assets/Buttons and States.svg similarity index 100% rename from other/Nostr Icons & Buttons/Buttons and States.svg rename to other/nostr-brand-assets/Buttons and States.svg diff --git a/other/Nostr Icons & Buttons/Nostr Brackets-1.svg b/other/nostr-brand-assets/Nostr Brackets-1.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostr Brackets-1.svg rename to other/nostr-brand-assets/Nostr Brackets-1.svg diff --git a/other/Nostr Icons & Buttons/Nostr Brackets-2.svg b/other/nostr-brand-assets/Nostr Brackets-2.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostr Brackets-2.svg rename to other/nostr-brand-assets/Nostr Brackets-2.svg diff --git a/other/Nostr Icons & Buttons/Nostr Brackets.svg b/other/nostr-brand-assets/Nostr Brackets.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostr Brackets.svg rename to other/nostr-brand-assets/Nostr Brackets.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-1.svg b/other/nostr-brand-assets/Nostrich Head-1.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-1.svg rename to other/nostr-brand-assets/Nostrich Head-1.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-2.svg b/other/nostr-brand-assets/Nostrich Head-2.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-2.svg rename to other/nostr-brand-assets/Nostrich Head-2.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-3.svg b/other/nostr-brand-assets/Nostrich Head-3.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-3.svg rename to other/nostr-brand-assets/Nostrich Head-3.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-4.svg b/other/nostr-brand-assets/Nostrich Head-4.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-4.svg rename to other/nostr-brand-assets/Nostrich Head-4.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-5.svg b/other/nostr-brand-assets/Nostrich Head-5.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-5.svg rename to other/nostr-brand-assets/Nostrich Head-5.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-6.svg b/other/nostr-brand-assets/Nostrich Head-6.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-6.svg rename to other/nostr-brand-assets/Nostrich Head-6.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head-7.svg b/other/nostr-brand-assets/Nostrich Head-7.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head-7.svg rename to other/nostr-brand-assets/Nostrich Head-7.svg diff --git a/other/Nostr Icons & Buttons/Nostrich Head.svg b/other/nostr-brand-assets/Nostrich Head.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich Head.svg rename to other/nostr-brand-assets/Nostrich Head.svg diff --git a/other/Nostr Icons & Buttons/Nostrich-1.svg b/other/nostr-brand-assets/Nostrich-1.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich-1.svg rename to other/nostr-brand-assets/Nostrich-1.svg diff --git a/other/Nostr Icons & Buttons/Nostrich-2.svg b/other/nostr-brand-assets/Nostrich-2.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich-2.svg rename to other/nostr-brand-assets/Nostrich-2.svg diff --git a/other/Nostr Icons & Buttons/Nostrich-3.svg b/other/nostr-brand-assets/Nostrich-3.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich-3.svg rename to other/nostr-brand-assets/Nostrich-3.svg diff --git a/other/Nostr Icons & Buttons/Nostrich.svg b/other/nostr-brand-assets/Nostrich.svg similarity index 100% rename from other/Nostr Icons & Buttons/Nostrich.svg rename to other/nostr-brand-assets/Nostrich.svg diff --git a/other/Nostr Icons & Buttons/Original-1.svg b/other/nostr-brand-assets/Original-1.svg similarity index 100% rename from other/Nostr Icons & Buttons/Original-1.svg rename to other/nostr-brand-assets/Original-1.svg diff --git a/other/Nostr Icons & Buttons/Original-2.svg b/other/nostr-brand-assets/Original-2.svg similarity index 100% rename from other/Nostr Icons & Buttons/Original-2.svg rename to other/nostr-brand-assets/Original-2.svg diff --git a/other/Nostr Icons & Buttons/Original-3.svg b/other/nostr-brand-assets/Original-3.svg similarity index 100% rename from other/Nostr Icons & Buttons/Original-3.svg rename to other/nostr-brand-assets/Original-3.svg diff --git a/other/Nostr Icons & Buttons/Original.svg b/other/nostr-brand-assets/Original.svg similarity index 100% rename from other/Nostr Icons & Buttons/Original.svg rename to other/nostr-brand-assets/Original.svg diff --git a/other/Nostr Icons & Buttons/Production-Ready Icons.svg b/other/nostr-brand-assets/Production-Ready Icons.svg similarity index 100% rename from other/Nostr Icons & Buttons/Production-Ready Icons.svg rename to other/nostr-brand-assets/Production-Ready Icons.svg diff --git a/other/Nostr Icons & Buttons/Shaka-1.svg b/other/nostr-brand-assets/Shaka-1.svg similarity index 100% rename from other/Nostr Icons & Buttons/Shaka-1.svg rename to other/nostr-brand-assets/Shaka-1.svg diff --git a/other/Nostr Icons & Buttons/Shaka-2.svg b/other/nostr-brand-assets/Shaka-2.svg similarity index 100% rename from other/Nostr Icons & Buttons/Shaka-2.svg rename to other/nostr-brand-assets/Shaka-2.svg diff --git a/other/Nostr Icons & Buttons/Shaka-3.svg b/other/nostr-brand-assets/Shaka-3.svg similarity index 100% rename from other/Nostr Icons & Buttons/Shaka-3.svg rename to other/nostr-brand-assets/Shaka-3.svg diff --git a/other/Nostr Icons & Buttons/Shaka.svg b/other/nostr-brand-assets/Shaka.svg similarity index 100% rename from other/Nostr Icons & Buttons/Shaka.svg rename to other/nostr-brand-assets/Shaka.svg From ec182a4c4deb03a9bc41c2da8987f2e50d2f8e65 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 17:18:25 +0100 Subject: [PATCH 018/239] some docs --- README.md | 10 +++++++++- docs/README.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 docs/README.md diff --git a/README.md b/README.md index 757e378..de7c287 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,12 @@ laseca is a social bitcoin to cash exchange, implemented as a webapp. + Run `npm install` + You can now start the app in a container by running `npm run start:container` (and shut it down with `npm run stop:container`). * Running + + Copy the `.env.dist` file into `.env` and set any values you like. + The app will run in a single container, with a Postgres database, a caddy webserver and the nodejs app. + Note that the container doesn't come with a volume for Postgres: default behaviour is to start from scratch every time you create the container, delete everything every time you delete the container. + The docker image launches the nodejs app with nodemon, so changes to the code will be available immediately. + + The Postgres database is reachable from the host, so you can use your favourite SQL client to access it. + + You can format with `npm run format` and lint with `npm run lint`. ## How to deploy @@ -25,6 +28,11 @@ The (hypothetical) approach is to: * Configure them all nice and stuff so they work together and have a reasonable production set up. * Set up the node app with systemd so it runs proper. +## Docs + +You can find documentation under `docs/`. + ## Other -* You can format with `npm run format` and lint with `npm run lint`. +* `other/branding` contains brand assets of laseca. +* `other/nostr-brand-assets` contains some nostr-related open source icons, logos, etc. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..04f38d8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,28 @@ +# laseca docs + +This is the documentation of laseca. It's targeted at devs and operators. + +## Overview + +laseca is a simple webapp. It follows a pretty standard setup: +* A postgres database holds the data of the application. +* A NodeJS+ExpressJS app serves both UI pages and API endpoints for the app. +* We suggest serving with Caddy, but any web server should do. + +## Sessions, users and authentication + +All users of laseca must have a nostr identity. Nostr identities consist on a public-private key pair. + +Upon visiting any page, the requester gets a session cookie in the form of a UUID7 record. Sessions can be authenticated, which allow the users to enter the app itself. Unauthenticated users can only visit the login page and some exceptional pages, if they do have the URL. + +Signups are not public: individual invites are provided, each identified with a UUID7. Users use invites by visiting an invite page and using it to signup with their Nostr keypair. The invite URLs are public, but since they hold a UUID7 in them, only people that have been given the URL can truly go in. + +When the user signs up or logs in, they are asked to answer a challenge with their Nostr web extension. Successfully signing the challenge with their key identifies them with it, and also increases the privileges of the session. + +Should a cookie expire, the user will get a new one, and will have to go through the login again to ensure it has enough privileges. + +Invites can be created through the CLI. + +## CLI + +The app comes with a CLI designed to be used by the laseca instance operator. The CLI is highly privileged and can perform powerful actions. Do not provide anyone with access to it. From df03aec36d3d7131de90b1cc9d57e885ec51e116 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 21 Feb 2025 17:18:34 +0100 Subject: [PATCH 019/239] rename app, not a hello world anymore --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9f252f7..05c2f32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "express-hello-world", - "version": "1.0.0", + "name": "laseca", + "version": "0.1.0", "dependencies": { "commander": "^13.1.0", "cookie-parser": "^1.4.7", From 9aa569281bffee0a8e667d22863c5cb3c616faf0 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 00:21:18 +0100 Subject: [PATCH 020/239] upcoming work --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index de7c287..1cfe8a9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ laseca is a social bitcoin to cash exchange, implemented as a webapp. +## Upcoming stories + +* [ ] Create offer +* [ ] Display existing offers +* [ ] Archive offer + ## How to set up dev environment * Pre-requisites From 0d737aa5fbb094a6ff20b7ac5fce6403808f2c0d Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 00:34:18 +0100 Subject: [PATCH 021/239] some visual edits --- src/routes/webRoutes.js | 10 ++++++++++ src/views/createOffer.ejs | 25 +++++++++++++++++++++++++ src/views/home.ejs | 16 +++++----------- 3 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 src/views/createOffer.ejs diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 0e0b60d..fd6585e 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -61,4 +61,14 @@ router.get( } ); +router.get( + '/offers', + redirectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, + (req, res) => { + res.render('offers', {}); + } +); + module.exports = router; diff --git a/src/views/createOffer.ejs b/src/views/createOffer.ejs new file mode 100644 index 0000000..3ea5150 --- /dev/null +++ b/src/views/createOffer.ejs @@ -0,0 +1,25 @@ + + + + Seca home + + + + + + +
+

la seca

+ + + + +
+
+ +
+ + + diff --git a/src/views/home.ejs b/src/views/home.ejs index ecb8241..175f08f 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -4,22 +4,16 @@ Seca home - +

la seca

-

Bienvenido a la seca

-
-
- - - -
-
-

Compraventas activas

-

Vaya, parece que no hay nada...

+ + + +
From e31225594be204aebed34902b045cc46a748b4f5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 00:37:53 +0100 Subject: [PATCH 022/239] delete old stuff --- src/public/javascript/createProfile.js | 123 ------------------------- 1 file changed, 123 deletions(-) diff --git a/src/public/javascript/createProfile.js b/src/public/javascript/createProfile.js index bfb9ac5..f7af1f1 100644 --- a/src/public/javascript/createProfile.js +++ b/src/public/javascript/createProfile.js @@ -1,126 +1,3 @@ -/* class ContactDetails { - constructor(rootUiElement) { - this.rootUiElement = rootUiElement; - this.details = []; - } - - addDetails(type, value) { - this.details.push({ type, value }); - } - - removeDetails(type, value) { - this.details = this.details.filter( - (detail) => detail.type !== type || detail.value !== value - ); - } - - syncUi() { - requestAnimationFrame(() => { - this.rootUiElement.innerHTML = ''; - this.details.forEach((detail) => { - const addedDetailFragment = this.buildContactDetailBadge(detail); - this.rootUiElement.appendChild(addedDetailFragment); - }); - }); - } - - buildContactDetailBadge(detail) { - const fragment = document.createDocumentFragment(); - - const div = document.createElement('div'); - div.className = 'added-contact-detail badge'; - - const p = document.createElement('p'); - p.textContent = `${detail.type}: ${detail.value}`; - - const button = document.createElement('button'); - button.textContent = 'Eliminar'; - button.onclick = () => { - this.removeDetails(detail.type, detail.value); - this.syncUi(); - return false; - }; - - div.appendChild(p); - div.appendChild(button); - fragment.appendChild(div); - - return fragment; - } - - async getEncryptedContactDetails() { - const jsonString = JSON.stringify(this.details); - const encryptedContactDetails = await window.nostr.nip04.encrypt( - await window.nostr.getPublicKey(), - jsonString - ); - return encryptedContactDetails; - } -} - -let contactDetails; - -window.onload = () => { - contactDetails = new ContactDetails( - document.querySelector('#created-contact-details-list') - ); - - document.querySelectorAll('.contact-detail-add-button').forEach((button) => { - button.addEventListener('click', function () { - const badge = this.parentElement; - const type = badge.getAttribute('data-type'); - const input = badge.querySelector('input'); - const value = input.value.trim(); - - if (value === '') return; - - contactDetails.addDetails(type, value); - contactDetails.syncUi(); - - input.value = ''; - }); - }); - - document - .querySelector('#submit-details-button') - .addEventListener('click', async () => { - const encryptedContactDetails = - await contactDetails.getEncryptedContactDetails(); - await fetch('/api/set-contact-details', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ encryptedContactDetails }), - }); - - const nym = document.querySelector('#nym-input').value; - await fetch('/api/set-nym', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ nym }), - }); - - setTimeout(() => { - window.location.href = '/home'; - }, 1000); - }); -}; - */ -/* - -Behaviours: - -- Validate inputs -- Only activate submit profile when nym and one contact method are alive -- Send the data and redirect on button click - - - -*/ - function debounce(func, wait) { let timeout; return function (...args) { From cc790f9efae5aa0f51ea8cf02110634233621058 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 01:10:22 +0100 Subject: [PATCH 023/239] starting app pages --- src/public/javascript/app.js | 10 +++++++++ src/public/javascript/home.js | 0 src/public/javascript/offers.js | 0 src/views/home.ejs | 11 ++++++---- src/views/offers.ejs | 29 +++++++++++++++++++++++++ src/views/partials/appCommonScripts.ejs | 1 + 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 src/public/javascript/app.js create mode 100644 src/public/javascript/home.js create mode 100644 src/public/javascript/offers.js create mode 100644 src/views/offers.ejs create mode 100644 src/views/partials/appCommonScripts.ejs diff --git a/src/public/javascript/app.js b/src/public/javascript/app.js new file mode 100644 index 0000000..aa28857 --- /dev/null +++ b/src/public/javascript/app.js @@ -0,0 +1,10 @@ +const navbuttonHome = document.getElementById('navbutton-home'); +const navbuttonOffers = document.getElementById('navbutton-offers'); + +navbuttonHome.addEventListener('click', () => { + window.location.href = '/home'; +}); + +navbuttonOffers.addEventListener('click', () => { + window.location.href = '/offers'; +}); diff --git a/src/public/javascript/home.js b/src/public/javascript/home.js new file mode 100644 index 0000000..e69de29 diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js new file mode 100644 index 0000000..e69de29 diff --git a/src/views/home.ejs b/src/views/home.ejs index 175f08f..f774225 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -10,10 +10,13 @@

la seca

- - - - + + + +
+ + <%- include("partials/appCommonScripts") %> + diff --git a/src/views/offers.ejs b/src/views/offers.ejs new file mode 100644 index 0000000..468cc50 --- /dev/null +++ b/src/views/offers.ejs @@ -0,0 +1,29 @@ + + + + Seca home + + + + + + +
+

la seca

+ + + + +
+ +
+ +
+

Let's create your offer

+
+
+ + <%- include("partials/appCommonScripts") %> + + + diff --git a/src/views/partials/appCommonScripts.ejs b/src/views/partials/appCommonScripts.ejs new file mode 100644 index 0000000..1df2d83 --- /dev/null +++ b/src/views/partials/appCommonScripts.ejs @@ -0,0 +1 @@ + From b0bac5e100463938ab97d7da14e409a02cba90c4 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 01:11:55 +0100 Subject: [PATCH 024/239] shared header --- src/views/home.ejs | 9 ++------- src/views/offers.ejs | 8 +------- src/views/partials/appCommonHeader.ejs | 7 +++++++ 3 files changed, 10 insertions(+), 14 deletions(-) create mode 100644 src/views/partials/appCommonHeader.ejs diff --git a/src/views/home.ejs b/src/views/home.ejs index f774225..b00f892 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -8,13 +8,8 @@ -
-

la seca

- - - - -
+ <%- include("partials/appCommonHeader") %> + <%- include("partials/appCommonScripts") %> diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 468cc50..1f6d05f 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -8,13 +8,7 @@ -
-

la seca

- - - - -
+ <%- include("partials/appCommonHeader") %>
diff --git a/src/views/partials/appCommonHeader.ejs b/src/views/partials/appCommonHeader.ejs new file mode 100644 index 0000000..0751ca5 --- /dev/null +++ b/src/views/partials/appCommonHeader.ejs @@ -0,0 +1,7 @@ +
+

la seca

+ + + + +
\ No newline at end of file From 2f092d6998cc4610d46f18505a2dec4f16b34174 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 01:45:55 +0100 Subject: [PATCH 025/239] models for offer info --- src/models/offerCreated.js | 27 +++++++++++++++ src/models/offerDetailsSet.js | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/models/offerCreated.js create mode 100644 src/models/offerDetailsSet.js diff --git a/src/models/offerCreated.js b/src/models/offerCreated.js new file mode 100644 index 0000000..fd67249 --- /dev/null +++ b/src/models/offerCreated.js @@ -0,0 +1,27 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../database'); + +const OfferCreated = sequelize.define( + 'OfferCreated', + { + uuid: { + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'offer_created', + } +); + +module.exports = OfferCreated; diff --git a/src/models/offerDetailsSet.js b/src/models/offerDetailsSet.js new file mode 100644 index 0000000..04e0cac --- /dev/null +++ b/src/models/offerDetailsSet.js @@ -0,0 +1,63 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../database'); + +const OfferDetailsSet = sequelize.define( + 'OfferDetailsSet', + { + uuid: { + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + offer_uuid: { + type: DataTypes.STRING, + allowNull: false, + }, + wants: { + type: DataTypes.STRING, + allowNull: false, + }, + premium: { + type: DataTypes.NUMBER, + allowNull: false, + }, + min_trade_amount_eur: { + type: DataTypes.NUMBER, + allowNull: false, + }, + max_trade_amount_eur: { + type: DataTypes.NUMBER, + allowNull: false, + }, + location_details: { + type: DataTypes.TEXT, + allowNull: false, + }, + time_availability_details: { + type: DataTypes.TEXT, + allowNull: false, + }, + is_onchain_accepted: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + is_lightning_accepted: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + are_big_notes_accepted: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'offer_details_set', + } +); + +module.exports = OfferDetailsSet; From bab9e6ad8729079d9af49e0c01706dbef8204010 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 01:46:58 +0100 Subject: [PATCH 026/239] format --- src/views/home.ejs | 1 - src/views/partials/appCommonHeader.ejs | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/views/home.ejs b/src/views/home.ejs index b00f892..9584e63 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -9,7 +9,6 @@ <%- include("partials/appCommonHeader") %> - <%- include("partials/appCommonScripts") %> diff --git a/src/views/partials/appCommonHeader.ejs b/src/views/partials/appCommonHeader.ejs index 0751ca5..332d748 100644 --- a/src/views/partials/appCommonHeader.ejs +++ b/src/views/partials/appCommonHeader.ejs @@ -1,7 +1,7 @@
-

la seca

- - - - -
\ No newline at end of file +

la seca

+ + + + +
From cb82d39ea58e909ce2a5e7fd482e0c459948fe2b Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 01:53:14 +0100 Subject: [PATCH 027/239] open new offer pane --- src/public/javascript/offers.js | 8 ++++++++ src/views/offers.ejs | 11 ++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index e69de29..dcae693 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -0,0 +1,8 @@ +const buttonStartCreateOffer = document.getElementById( + 'button-start-create-offer' +); +const createOfferControls = document.getElementById('create-offer-controls'); + +buttonStartCreateOffer.addEventListener('click', () => { + createOfferControls.style.display = 'block'; +}); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 1f6d05f..f225e28 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -11,9 +11,14 @@ <%- include("partials/appCommonHeader") %>
- -
-

Let's create your offer

+ +
From 77785a21044166e981e06d41162c0f7e58174628 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 22 Feb 2025 02:02:14 +0100 Subject: [PATCH 028/239] toggle controls --- src/public/css/seca.css | 3 +++ src/public/img/circle-xmark.svg | 40 +++++++++++++++++++++++++++++++++ src/public/javascript/offers.js | 13 ++++++++++- src/views/offers.ejs | 3 +++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/public/img/circle-xmark.svg diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 4cebba8..2023cfb 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -24,6 +24,9 @@ h1 { color: #e1c300; } +.clickable { + cursor: pointer; +} .over-background { background-color: white; border-radius: 1vw; diff --git a/src/public/img/circle-xmark.svg b/src/public/img/circle-xmark.svg new file mode 100644 index 0000000..9ee593d --- /dev/null +++ b/src/public/img/circle-xmark.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index dcae693..05a1caf 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -1,8 +1,19 @@ const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); +const closeOfferControls = document.getElementById('close-order-controls-x'); const createOfferControls = document.getElementById('create-offer-controls'); buttonStartCreateOffer.addEventListener('click', () => { - createOfferControls.style.display = 'block'; + toggleCreateOfferControls(); }); + +closeOfferControls.addEventListener('click', () => { + toggleCreateOfferControls(); +}); + +function toggleCreateOfferControls() { + createOfferControls.style.display = + createOfferControls.style.display === 'block' ? 'none' : 'block'; + buttonStartCreateOffer.disabled = !buttonStartCreateOffer.disabled; +} diff --git a/src/views/offers.ejs b/src/views/offers.ejs index f225e28..566654b 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -15,7 +15,10 @@ Crear nueva oferta +
+

¿Cuánto?

+ +
+
+

¿Cómo se mueve el Bitcoin?

+ + +
From adb393f74076e8919f230252b162934a98716383 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 23 Feb 2025 23:41:13 +0100 Subject: [PATCH 033/239] money amount --- src/public/css/seca.css | 12 ++++++++++++ src/public/javascript/offers.js | 23 +++++++++++++++++++++++ src/views/offers.ejs | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 6a84a26..7a13e11 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -28,6 +28,18 @@ h1 { cursor: pointer; } +.money-input { + text-align: end; +} + +.input-is-valid { + border: 1px solid green; +} + +.input-is-invalid { + border: 2px solid red; +} + .button-group button { border: 0; padding: 1em; diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 28b78eb..d8a6e0e 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -18,6 +18,8 @@ const buttonDecreasePremium = document.getElementById( 'button-decrease-premium' ); +const eurAmountInput = document.getElementById('input-eur-amount'); + function toggleCreateOfferControls() { createOfferControls.style.display = createOfferControls.style.display === 'block' ? 'none' : 'block'; @@ -51,6 +53,23 @@ function toggleBuyOrSellButtonGroup() { }); } +function validateAndFormatEurAmountInput() { + const eurAmountFieldValue = eurAmountInput.value; + const regularExpression = /€?\s?(\d+[\.,]?\d*)\s?€?/; + const matchResult = eurAmountFieldValue.match(regularExpression); + + eurAmountInput.classList.remove('input-is-valid', 'input-is-invalid'); + if (matchResult) { + const numberString = matchResult[1]; + const cleanInputNumber = parseInt(numberString); + eurAmountInput.value = `${cleanInputNumber} €`; + eurAmountInput.classList.add('input-is-valid'); + return; + } + + eurAmountInput.classList.add('input-is-invalid'); +} + buyOrSellButtons.forEach((button) => { button.addEventListener('click', () => { toggleBuyOrSellButtonGroup(); @@ -64,3 +83,7 @@ buttonIncreasePremium.addEventListener('click', () => { buttonDecreasePremium.addEventListener('click', () => { modifyPremiumValue(-1); }); + +eurAmountInput.addEventListener('blur', () => { + validateAndFormatEurAmountInput(); +}); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index ef604c6..205567b 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -41,7 +41,7 @@

¿Cuánto?

- +

¿Cómo se mueve el Bitcoin?

From b3cb690c3df8fe35a1ba3dec1424db804a3ca6dd Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 24 Feb 2025 00:31:17 +0100 Subject: [PATCH 034/239] fixes bloody sizing --- src/public/css/offers.css | 28 ++++++++++++++++++++++++++++ src/public/javascript/offers.js | 2 +- src/views/offers.ejs | 18 +++++++++++++++--- 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/public/css/offers.css diff --git a/src/public/css/offers.css b/src/public/css/offers.css new file mode 100644 index 0000000..b0e9be0 --- /dev/null +++ b/src/public/css/offers.css @@ -0,0 +1,28 @@ +#eur-amount { + padding: 0; + width: 50%; +} + +#eur-amount > * { + display: inline-block; + font-size: 1em; + height: 2em; + vertical-align: middle; +} + +#input-eur-amount { + width:75%; +} + +#eur-amount #eur-symbol { + background-color: #d8d8d8; + width: 25%; +} + +#eur-amount #eur-symbol #eur-character { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index d8a6e0e..db01c0a 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -55,7 +55,7 @@ function toggleBuyOrSellButtonGroup() { function validateAndFormatEurAmountInput() { const eurAmountFieldValue = eurAmountInput.value; - const regularExpression = /€?\s?(\d+[\.,]?\d*)\s?€?/; + const regularExpression = /€?\s?(\d+[.,]?\d*)\s?€?/; const matchResult = eurAmountFieldValue.match(regularExpression); eurAmountInput.classList.remove('input-is-valid', 'input-is-invalid'); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 205567b..4ec4fbe 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -5,6 +5,7 @@ + @@ -41,12 +42,23 @@

¿Cuánto?

- +
+
+ +
+

¿Cómo se mueve el Bitcoin?

- - + +

¿Cuánto?

-
+
- + /> +
+ +
+
+
+ + +
+ SAT
From 86b1198e5d2dcb2513e2cf708e593880b768f110 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 24 Feb 2025 11:22:56 +0100 Subject: [PATCH 036/239] check the checkboxes --- src/views/offers.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 15e9669..ccb417b 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -69,9 +69,9 @@

¿Cómo se mueve el Bitcoin?

+ > + >
+
+

¿Quién puede ver la oferta?

+ + +
+
+ +
From bda4277d137f201ed416eadd60facefb5b47d3a8 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 24 Feb 2025 16:04:22 +0100 Subject: [PATCH 040/239] styling thingies --- src/public/css/offers.css | 35 ++++++++++++++++++++++++++ src/public/css/seca.css | 34 +++++++++++++++++++++++-- src/public/javascript/offers.js | 6 +++-- src/views/offers.ejs | 12 +++++---- src/views/partials/appCommonHeader.ejs | 8 +++--- 5 files changed, 82 insertions(+), 13 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 087f59f..56f29d9 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -1,3 +1,38 @@ +@media (max-width: 768px) { + #offers-root { + width: 100%; + margin: 10px 10px; + } + + #close-offer-controls-x { + width: 25px; + } +} + +@media (min-width: 769px) { + #offers-root { + width: 75%; + margin: 20px auto; + } + + #close-offer-controls-x { + width: 50px; + } +} + + +#create-offer-controls > * { + text-align: center; + margin-left: 15%; + margin-right: 15%; + width: 70% +} + +#close-offer-controls-area { + display: flex; + justify-content: end; +} + .money-amount-input-area { padding: 0; width: 10em; diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 7a13e11..5cd14da 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -3,6 +3,38 @@ box-sizing: border-box; } +@media (max-width: 768px) { + html { + font-size: 16px; + } + + .font-medium { + font-size: 0.7rem; + } + + .button-medium { + height: 3em; + padding: 1em; + border-radius: 10px; + } +} + +@media (min-width: 769px) { + html { + font-size: 20px; + } + + .font-medium { + font-size: 0.9rem; + } + + .button-medium { + height: 3em; + padding: 1em; + border-radius: 10px; + } +} + .badges { display: grid; grid-template-columns: repeat(2, 1fr); @@ -106,10 +138,8 @@ h1 { .button-primary { background: #e1c300; - border-radius: 1vw; border: 0; color: white; - padding: 1%; cursor: pointer; } diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index b7c8b0c..c91ba15 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -1,7 +1,7 @@ const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); -const closeOfferControls = document.getElementById('close-order-controls-x'); +const closeOfferControls = document.getElementById('close-offer-controls-x'); const createOfferControls = document.getElementById('create-offer-controls'); const buyOrSellButtonGroup = document.getElementById( @@ -32,7 +32,9 @@ const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; function toggleCreateOfferControls() { createOfferControls.style.display = createOfferControls.style.display === 'block' ? 'none' : 'block'; - buttonStartCreateOffer.disabled = !buttonStartCreateOffer.disabled; + + buttonStartCreateOffer.style.display = + buttonStartCreateOffer.style.display === 'block' ? 'none' : 'block'; } function modifyPremiumValue(delta) { diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 3796dd9..68f26e2 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -11,17 +11,19 @@ <%- include("partials/appCommonHeader") %> -
- -
-

¿Cuánto?

-
- -
- +

¿Cuánto?

+
+
+ +
+ +
-
-
- -
- SAT +
+ +
+ SAT +
From 469c1e6d363e7a4f17be26144afeb6bc71860795 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 24 Feb 2025 17:20:35 +0100 Subject: [PATCH 043/239] pretty premium selector --- src/public/css/offers.css | 24 +++++++++++------------- src/views/offers.ejs | 12 +++++++----- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 90ccc11..9f17d50 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -48,30 +48,30 @@ width: 80%; margin-left: auto; margin-right: auto; -} - -#premium-content-area > * { - display: inline-block; - margin: 0 auto; -} - -#premium-selector-area > * { - display: inline-block; + display: flex; } #premium-selector-area { width: 23%; margin-left: 0; margin-right: 5%; + display: flex; } #premium-value { border: 1px solid gray; - width: 100%; + width: 50%; + align-content: center; +} + +#premium-buttons-container { + width:50%; } .premium-button { - width: 45%; + width: 100%; + height: 50%; + font-size: 1em; } #premium-price-display-area { @@ -86,8 +86,6 @@ margin-right: auto; } - - .money-amount-input-area { padding: 0; width: 100%; diff --git a/src/views/offers.ejs b/src/views/offers.ejs index f802a7c..566f33b 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -48,11 +48,13 @@
0%
- +
+ +

From 4b7df27e40cf5dde8450b813d174f57d7091929e Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 24 Feb 2025 17:39:09 +0100 Subject: [PATCH 044/239] align stuff --- src/public/css/offers.css | 20 +++++++++++++++++++- src/views/offers.ejs | 32 +++++++++++++++++--------------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 9f17d50..782859f 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -65,7 +65,7 @@ } #premium-buttons-container { - width:50%; + width: 50%; } .premium-button { @@ -117,3 +117,21 @@ width: 100%; height: 100%; } + +.checkbox-row { + display: flex; + width: 25%; + margin-left: auto; + margin-right: auto; +} +.checkbox-row > label { + text-align: start; + margin-left: 0; + align-content: center; +} + +.checkbox-row > input[type='checkbox'] { + margin-left: 20%; + height: 2em; + width: 2em; +} diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 566f33b..96c91e2 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -94,21 +94,23 @@

-

¿Cómo se mueve el Bitcoin?

- - +

¿Cómo se mueve el Bitcoin?

+
+ +
+
+ +
From 7db6eef0d1baac189ba0bed43f9aa13ee926c099 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 24 Feb 2025 23:47:01 +0100 Subject: [PATCH 045/239] stuff --- src/public/css/offers.css | 38 ++++++++++++++++++---- src/public/css/seca.css | 2 +- src/views/offers.ejs | 66 ++++++++++++++++++++++----------------- 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 782859f..f195e0c 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -1,6 +1,5 @@ @media (max-width: 768px) { #offers-root { - width: 100%; margin: 10px 10px; } @@ -11,6 +10,19 @@ #premium-selector-area { width: 60px; } + + #create-offer-controls > * { + width: 100%; + } + + .checkbox-row { + width: 90%; + } + + .checkbox-row > input[type='checkbox'] { + flex-shrink: 0; + margin-left: 5%; + } } @media (min-width: 769px) { @@ -26,13 +38,25 @@ #premium-selector-area { width: 100px; } + + #create-offer-controls > * { + margin-left: 15%; + margin-right: 15%; + width: 70%; + } + + .checkbox-row { + width: 60%; + } + + .checkbox-row > input[type='checkbox'] { + flex-shrink: 0; + margin-left: 20%; + } } #create-offer-controls > * { text-align: center; - margin-left: 15%; - margin-right: 15%; - width: 70%; } #close-offer-controls-area { @@ -120,18 +144,20 @@ .checkbox-row { display: flex; - width: 25%; margin-left: auto; margin-right: auto; } .checkbox-row > label { text-align: start; + margin-right: auto; margin-left: 0; align-content: center; } .checkbox-row > input[type='checkbox'] { - margin-left: 20%; + flex-shrink: 0; + margin-right: 1em; height: 2em; width: 2em; } + diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 8214815..86da51f 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -21,7 +21,7 @@ @media (min-width: 769px) { html { - font-size: 20px; + font-size: 16px; } .font-medium { diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 96c91e2..7432b8b 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -116,37 +116,45 @@

-

¿Quién puede ver la oferta?

- - - +

¿Quién puede ver la oferta?

+
+ +
+
+ +
+
+ +
- +

Extras

+
+ +
+
<%- include("partials/appCommonScripts") %> From a5181f8be1dc39adfb14b97d3e1758197991009b Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 00:11:20 +0100 Subject: [PATCH 047/239] style premium buttons --- src/public/css/offers.css | 19 ++++++++++++++++++- src/public/javascript/offers.js | 2 +- src/views/offers.ejs | 13 +++++-------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 11ee723..bef2a94 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -65,7 +65,6 @@ flex-shrink: 0; margin-left: 20%; } - } #create-offer-controls > * { @@ -105,6 +104,24 @@ width: 50%; } +.premium-button { + background: white; + border: 2px solid #e1c300; +} + +.premium-button:hover { + background: #fff8ce; +} + +#button-increase-premium { + border-top-right-radius: 10px +} + +#button-decrease-premium { + border-bottom-right-radius: 10px +} + + .premium-button { width: 100%; height: 50%; diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index c91ba15..3f974bc 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -139,7 +139,7 @@ eurAmountInput.addEventListener('input', () => { eurAmountInput.value = eurAmountInput.value.replace(/[^0-9]/g, ''); }); -for (btcMethodCheckbox of btcMethodCheckboxes) { +for (const btcMethodCheckbox of btcMethodCheckboxes) { btcMethodCheckbox.addEventListener('input', () => { validateBitcoinMethodCheckboxes(); }); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 029d7bf..04f81b3 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -20,10 +20,7 @@ Crear nueva oferta -
From d308d2f26dd136dcef71b47f79d77eb1938a4052 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 00:38:13 +0100 Subject: [PATCH 048/239] more details --- src/public/css/offers.css | 24 ++++++++++++++++++++---- src/public/javascript/offers.js | 1 + src/views/offers.ejs | 12 ++++++------ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index bef2a94..a7a3549 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -82,14 +82,15 @@ #premium-content-area { width: 80%; + height: 50px; + align-items: center; margin-left: auto; margin-right: auto; display: flex; } #premium-selector-area { - width: 23%; - margin-left: 0; + margin-left: auto; margin-right: 5%; display: flex; } @@ -130,8 +131,19 @@ #premium-price-display-area { margin-left: 0; - margin-right: 0; - width: 70%; + margin-right: auto; + font-size: 1em; + text-align: start; +} + +#premium-price-display-area > * { + margin-top: 0; + margin-bottom: 0; +} + +#market-price-paragraph { + color: gray; + font-size: 0.8em; } #amount-area-content { @@ -190,6 +202,10 @@ width: 2em; } +#at-least-one-checkbox { + color: red; +} + #submit-button-area { margin-top: 1em; margin-bottom: 1em; diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 3f974bc..8d0104c 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -137,6 +137,7 @@ eurAmountInput.addEventListener('blur', () => { eurAmountInput.addEventListener('input', () => { eurAmountInput.value = eurAmountInput.value.replace(/[^0-9]/g, ''); + updateBtcInput(); }); for (const btcMethodCheckbox of btcMethodCheckboxes) { diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 04f81b3..f8928e9 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -54,10 +54,11 @@
-

+

Tu precio: 90 000€/BTC - (Precio mercado: 83 000€/BTC) +

+

(Precio mercado: 83 000€/BTC)

@@ -69,9 +70,9 @@ id="input-eur-amount" type="text" class="money-input input-money-amount" + placeholder="100" required - /> + />
@@ -82,8 +83,7 @@ type="text" class="money-input input-money-amount" disabled - /> + />
SAT
From fad4e7a174b1b11f0a8e7775e908070d4a98f842 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 00:52:25 +0100 Subject: [PATCH 049/239] checkbox can't be unselected --- src/public/css/seca.css | 2 +- src/public/javascript/offers.js | 25 ++++++++++--------------- src/views/offers.ejs | 4 ++-- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 8704708..629ba1a 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -1,5 +1,5 @@ * { - font-family: 'Courier New', Courier, monospace; + font-family: Arial, Helvetica, sans-serif; box-sizing: border-box; } diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 8d0104c..eaa49cc 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -29,6 +29,8 @@ const btcMethodCheckboxesWarning = document.getElementById( const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; +const publishOfferButton = document.getElementById('button-submit-offer'); + function toggleCreateOfferControls() { createOfferControls.style.display = createOfferControls.style.display === 'block' ? 'none' : 'block'; @@ -92,19 +94,10 @@ function updateBtcInput() { btcAmountInput.value = formattedSatsAmount; } -function validateBitcoinMethodCheckboxes() { - let noneChecked = true; - - for (const aCheckbox of btcMethodCheckboxes) { - if (aCheckbox.checked) { - noneChecked = false; - } - } - - if (noneChecked) { - btcMethodCheckboxesWarning.style.display = 'block'; - } else { - btcMethodCheckboxesWarning.style.display = 'none'; +function validateBitcoinMethodCheckboxes(clickedCheckbox) { + let checkedCount = btcMethodCheckboxes.filter((cb) => cb.checked).length; + if (checkedCount === 0) { + clickedCheckbox.checked = true; } } @@ -141,7 +134,9 @@ eurAmountInput.addEventListener('input', () => { }); for (const btcMethodCheckbox of btcMethodCheckboxes) { - btcMethodCheckbox.addEventListener('input', () => { - validateBitcoinMethodCheckboxes(); + btcMethodCheckbox.addEventListener('click', () => { + validateBitcoinMethodCheckboxes(btcMethodCheckbox); }); } + +updateBtcInput(); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index f8928e9..ffa747d 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -70,7 +70,7 @@ id="input-eur-amount" type="text" class="money-input input-money-amount" - placeholder="100" + value="100" required />
@@ -162,7 +162,7 @@
<%- include("partials/appCommonScripts") %> - + From 5f914617998c6e37b3fa96d974c79a76b0c79827 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:01:25 +0100 Subject: [PATCH 050/239] start stitching --- src/models/offerDetailsSet.js | 6 +----- src/public/javascript/offers.js | 21 +++++++++++++++++++++ src/views/offers.ejs | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/models/offerDetailsSet.js b/src/models/offerDetailsSet.js index 04e0cac..2c34018 100644 --- a/src/models/offerDetailsSet.js +++ b/src/models/offerDetailsSet.js @@ -22,11 +22,7 @@ const OfferDetailsSet = sequelize.define( type: DataTypes.NUMBER, allowNull: false, }, - min_trade_amount_eur: { - type: DataTypes.NUMBER, - allowNull: false, - }, - max_trade_amount_eur: { + trade_amount_eur: { type: DataTypes.NUMBER, allowNull: false, }, diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index eaa49cc..0edcb0d 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -101,6 +101,23 @@ function validateBitcoinMethodCheckboxes(clickedCheckbox) { } } +function publishOffer() { + // read info from all elements + // compose a json + // hit the endpoint + + const offerDetails = { + wants: 'BTC', + premium: '0', + trade_amount_eur: '100', + location_details: 'this and there', + time_availability_details: 'then and then', + is_onchain_accepted: true, + is_lightning_accepted: true, + are_big_notes_accepted: + }; +} + buttonStartCreateOffer.addEventListener('click', () => { toggleCreateOfferControls(); }); @@ -139,4 +156,8 @@ for (const btcMethodCheckbox of btcMethodCheckboxes) { }); } +publishOfferButton.addEventListener('click', () => { + publishOffer(); +}); + updateBtcInput(); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index ffa747d..8dad674 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -58,7 +58,7 @@ Tu precio: 90 000€/BTC

-

(Precio mercado: 83 000€/BTC)

+

(Precio mercado: 83 000€/BTC)

From 7a6abbcb841d52df2614b4fd61f4ec7778fd2ed6 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:02:16 +0100 Subject: [PATCH 051/239] remove unused stuff --- src/public/css/offers.css | 4 ---- src/public/javascript/offers.js | 4 +--- src/views/offers.ejs | 3 --- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index a7a3549..828bd57 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -202,10 +202,6 @@ width: 2em; } -#at-least-one-checkbox { - color: red; -} - #submit-button-area { margin-top: 1em; margin-bottom: 1em; diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 0edcb0d..df4638e 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -23,9 +23,7 @@ const btcAmountInput = document.getElementById('input-btc-amount'); const onchainCheckbox = document.getElementById('onchain-checkbox'); const lightningCheckbox = document.getElementById('lightning-checkbox'); -const btcMethodCheckboxesWarning = document.getElementById( - 'at-least-one-checkbox' -); + const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 8dad674..b419154 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -108,9 +108,6 @@ checked /> -

¿Quién puede ver la oferta?

From 54f7aee60f15bd784a0a0dd1b45af895a4a09ed9 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:04:34 +0100 Subject: [PATCH 052/239] typo --- src/public/javascript/offers.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index df4638e..1879a8d 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -24,7 +24,6 @@ const btcAmountInput = document.getElementById('input-btc-amount'); const onchainCheckbox = document.getElementById('onchain-checkbox'); const lightningCheckbox = document.getElementById('lightning-checkbox'); - const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; const publishOfferButton = document.getElementById('button-submit-offer'); @@ -112,7 +111,7 @@ function publishOffer() { time_availability_details: 'then and then', is_onchain_accepted: true, is_lightning_accepted: true, - are_big_notes_accepted: + are_big_notes_accepted: false, }; } From e2ba49144ca02ba9b3696e1e4532e10fd60a7e32 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:04:41 +0100 Subject: [PATCH 053/239] area and title --- src/views/offers.ejs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index b419154..ccbe170 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -90,6 +90,9 @@
+
+

¿Dónde y cuándo?

+

¿Cómo se mueve el Bitcoin?

From 15116ac41f7c29307450ba96d8402ee8c723425b Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:19:12 +0100 Subject: [PATCH 054/239] textboxes --- src/views/offers.ejs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index ccbe170..ec47fbc 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -90,8 +90,12 @@
-
+

¿Dónde y cuándo?

+
+ + +

¿Cómo se mueve el Bitcoin?

From 9df242221ec51f7b19e6f3aac8f39dbdcdd4ae8f Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:19:19 +0100 Subject: [PATCH 055/239] textboxes --- src/views/offers.ejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index ec47fbc..17682bf 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -93,8 +93,8 @@

¿Dónde y cuándo?

- - + +
From 3495429102b1dcd8904f749ed12e2988d83b5748 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 14:47:27 +0100 Subject: [PATCH 056/239] good to gooo --- src/public/css/offers.css | 9 ++++++++- src/views/offers.ejs | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 828bd57..1b09f48 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -12,7 +12,7 @@ } #premium-selector-area { - width: 60px; + width: 80px; } #amount-area-content { @@ -183,6 +183,13 @@ height: 100%; } +.place-and-time-box { + height: 100px; + width: 30%; + min-width: 200px; + resize: none; +} + .checkbox-row { display: flex; margin-left: auto; diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 17682bf..fd19a56 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -93,8 +93,8 @@

¿Dónde y cuándo?

- - + +
From 2f67dc4a1ddedc9870aa43d87e738461044a6359 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 15:21:49 +0100 Subject: [PATCH 057/239] missing display details --- src/models/offerDetailsSet.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/models/offerDetailsSet.js b/src/models/offerDetailsSet.js index 2c34018..4d4eb51 100644 --- a/src/models/offerDetailsSet.js +++ b/src/models/offerDetailsSet.js @@ -34,6 +34,18 @@ const OfferDetailsSet = sequelize.define( type: DataTypes.TEXT, allowNull: false, }, + show_offer_to_trusted: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + show_offer_to_trusted_trusted: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, + show_offer_to_all_members: { + type: DataTypes.BOOLEAN, + allowNull: false, + }, is_onchain_accepted: { type: DataTypes.BOOLEAN, allowNull: false, From 54425f8ef9c5c1daed7e68ecc04dd4cd5488b9c5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 16:42:49 +0100 Subject: [PATCH 058/239] get all fields --- src/public/javascript/offers.js | 52 ++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 1879a8d..921bab6 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -8,6 +8,8 @@ const buyOrSellButtonGroup = document.getElementById( 'button-group-buy-or-sell' ); const buyOrSellButtons = buyOrSellButtonGroup.querySelectorAll('button'); +const buyButton = document.getElementById('button-buy-bitcoin'); +const sellButton = document.getElementById('button-sell-bitcoin'); const premiumValue = document.getElementById('premium-value'); const buttonIncreasePremium = document.getElementById( @@ -21,11 +23,24 @@ const buttonDecreasePremium = document.getElementById( const eurAmountInput = document.getElementById('input-eur-amount'); const btcAmountInput = document.getElementById('input-btc-amount'); +const placeInput = document.getElementById('place-input'); +const timeInput = document.getElementById('time-input'); + const onchainCheckbox = document.getElementById('onchain-checkbox'); const lightningCheckbox = document.getElementById('lightning-checkbox'); const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; +const mytrustedCheckbox = document.getElementById('my-trusted-checkbox'); +const mytrustedtrustdCheckbox = document.getElementById( + 'my-trusted-trusted-checkbox' +); +const allMembersCheckbox = document.getElementById('all-members-checkbox'); + +const bigNotesAcceptedCheckbox = document.getElementById( + 'large-bills-checkbox' +); + const publishOfferButton = document.getElementById('button-submit-offer'); function toggleCreateOfferControls() { @@ -99,20 +114,35 @@ function validateBitcoinMethodCheckboxes(clickedCheckbox) { } function publishOffer() { - // read info from all elements - // compose a json - // hit the endpoint + let wants; + if (buyButton.classList.contains('selected')) { + wants = 'BTC'; + } + if (sellButton.classList.contains('selected')) { + wants = 'EUR'; + } + + const premium = parseInt(premiumValue.innerText.match(/\d+/)[0]) / 100; + const trade_amount_eur = eurAmountInput.value; + const location_details = placeInput.value; + const time_availability_details = timeInput.value; + const is_onchain_accepted = onchainCheckbox.checked; + const is_lightning_accepted = lightningCheckbox.checked; + const are_big_notes_accepted = bigNotesAcceptedCheckbox.checked; const offerDetails = { - wants: 'BTC', - premium: '0', - trade_amount_eur: '100', - location_details: 'this and there', - time_availability_details: 'then and then', - is_onchain_accepted: true, - is_lightning_accepted: true, - are_big_notes_accepted: false, + wants, + premium, + trade_amount_eur, + location_details, + time_availability_details, + is_onchain_accepted, + is_lightning_accepted, + + are_big_notes_accepted, }; + + console.log(offerDetails); } buttonStartCreateOffer.addEventListener('click', () => { From b3fe695ee52cb7bbab6bbcf97a1f2351996f8faf Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 17:00:29 +0100 Subject: [PATCH 059/239] mock offer endpoint --- src/routes/apiRoutes.js | 18 ++++++++++++++++++ src/services/offerService.js | 6 ++++++ 2 files changed, 24 insertions(+) create mode 100644 src/services/offerService.js diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index f27c0a9..4b2a2d4 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -5,6 +5,7 @@ const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); const sessionService = require('../services/sessionService'); const profileService = require('../services/profileService'); +const offerService = require('../services/offerService'); const errors = require('../errors'); const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); const rejectIfNotAuthorizedMiddleware = require('../middlewares/rejectIfNotAuthorizedMiddleware'); @@ -216,4 +217,21 @@ router.post( } ); +router.post( + '/offer', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const publicKey = req.cookies.publicKey; + const { offerDetails } = req.body.offerDetails; + + await offerService.createOffer(publicKey, offerDetails); + + return res.status(200).json({ + success: true, + message: 'Offer created successfully', + }); + } +); + module.exports = router; diff --git a/src/services/offerService.js b/src/services/offerService.js new file mode 100644 index 0000000..302b3e2 --- /dev/null +++ b/src/services/offerService.js @@ -0,0 +1,6 @@ +async function createOffer(publicKey, offerDetails) { + console.log('oui oui, je suis offer creation oui'); + console.log(publicKey); + console.log(offerDetails); +} +module.exports = createOffer; From 5eb1ad97b456a63401a4a4905d08a27a7aca35a6 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 17:09:20 +0100 Subject: [PATCH 060/239] hit endpoint --- src/public/javascript/offers.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 921bab6..3a73d12 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -31,8 +31,8 @@ const lightningCheckbox = document.getElementById('lightning-checkbox'); const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; -const mytrustedCheckbox = document.getElementById('my-trusted-checkbox'); -const mytrustedtrustdCheckbox = document.getElementById( +const myTrustedCheckbox = document.getElementById('my-trusted-checkbox'); +const myTrustedTrustedCheckbox = document.getElementById( 'my-trusted-trusted-checkbox' ); const allMembersCheckbox = document.getElementById('all-members-checkbox'); @@ -113,7 +113,7 @@ function validateBitcoinMethodCheckboxes(clickedCheckbox) { } } -function publishOffer() { +async function publishOffer() { let wants; if (buyButton.classList.contains('selected')) { wants = 'BTC'; @@ -128,6 +128,9 @@ function publishOffer() { const time_availability_details = timeInput.value; const is_onchain_accepted = onchainCheckbox.checked; const is_lightning_accepted = lightningCheckbox.checked; + const show_offer_to_trusted = myTrustedCheckbox.checked; + const show_offer_to_trusted_trusted = myTrustedTrustedCheckbox.checked; + const show_offer_to_all_members = allMembersCheckbox.checked; const are_big_notes_accepted = bigNotesAcceptedCheckbox.checked; const offerDetails = { @@ -138,11 +141,19 @@ function publishOffer() { time_availability_details, is_onchain_accepted, is_lightning_accepted, - + show_offer_to_trusted, + show_offer_to_trusted_trusted, + show_offer_to_all_members, are_big_notes_accepted, }; - console.log(offerDetails); + await fetch('/api/offer', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ offerDetails }), + }); } buttonStartCreateOffer.addEventListener('click', () => { From 01e97951aacc9ce7f568434383ed6101da3908f0 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 17:15:08 +0100 Subject: [PATCH 061/239] wrong tags --- src/views/offers.ejs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index fd19a56..3192940 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -56,9 +56,8 @@

Tu precio: 90 000€/BTC -

-

(Precio mercado: 83 000€/BTC)

+

(Precio mercado: 83 000€/BTC)

From 4eef0a57e061e829c2dffa53ed4c0deb5eea0581 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 25 Feb 2025 17:15:20 +0100 Subject: [PATCH 062/239] format --- src/views/offers.ejs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 3192940..7bed996 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -57,7 +57,9 @@

Tu precio: 90 000€/BTC

-

(Precio mercado: 83 000€/BTC)

+

+ (Precio mercado: 83 000€/BTC) +

@@ -92,8 +94,20 @@

¿Dónde y cuándo?

- - + +
From b430b42b608a6eedbca1dde084679209813cda46 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 00:48:16 +0100 Subject: [PATCH 063/239] create offer stitched with backend --- .../{offerCreated.js => OfferCreated.js} | 0 ...{offerDetailsSet.js => OfferDetailsSet.js} | 4 +-- src/public/css/offers.css | 13 ++++---- src/routes/apiRoutes.js | 2 +- src/services/offerService.js | 32 ++++++++++++++++--- 5 files changed, 37 insertions(+), 14 deletions(-) rename src/models/{offerCreated.js => OfferCreated.js} (100%) rename src/models/{offerDetailsSet.js => OfferDetailsSet.js} (95%) diff --git a/src/models/offerCreated.js b/src/models/OfferCreated.js similarity index 100% rename from src/models/offerCreated.js rename to src/models/OfferCreated.js diff --git a/src/models/offerDetailsSet.js b/src/models/OfferDetailsSet.js similarity index 95% rename from src/models/offerDetailsSet.js rename to src/models/OfferDetailsSet.js index 4d4eb51..20ac456 100644 --- a/src/models/offerDetailsSet.js +++ b/src/models/OfferDetailsSet.js @@ -19,11 +19,11 @@ const OfferDetailsSet = sequelize.define( allowNull: false, }, premium: { - type: DataTypes.NUMBER, + type: DataTypes.DECIMAL(5, 2), allowNull: false, }, trade_amount_eur: { - type: DataTypes.NUMBER, + type: DataTypes.INTEGER, allowNull: false, }, location_details: { diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 1b09f48..16fad27 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -115,14 +115,13 @@ } #button-increase-premium { - border-top-right-radius: 10px + border-top-right-radius: 10px; } #button-decrease-premium { - border-bottom-right-radius: 10px + border-bottom-right-radius: 10px; } - .premium-button { width: 100%; height: 50%; @@ -184,10 +183,10 @@ } .place-and-time-box { - height: 100px; - width: 30%; - min-width: 200px; - resize: none; + height: 100px; + width: 30%; + min-width: 200px; + resize: none; } .checkbox-row { diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 4b2a2d4..547cdb3 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -223,7 +223,7 @@ router.post( attachPublicKeyMiddleware, async (req, res) => { const publicKey = req.cookies.publicKey; - const { offerDetails } = req.body.offerDetails; + const offerDetails = req.body.offerDetails; await offerService.createOffer(publicKey, offerDetails); diff --git a/src/services/offerService.js b/src/services/offerService.js index 302b3e2..cca3b39 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -1,6 +1,30 @@ +const uuid = require('uuid'); + +const OfferCreated = require('../models/OfferCreated'); +const OfferDetailsSet = require('../models/OfferDetailsSet'); + async function createOffer(publicKey, offerDetails) { - console.log('oui oui, je suis offer creation oui'); - console.log(publicKey); - console.log(offerDetails); + const offerCreated = await OfferCreated.create({ + uuid: uuid.v7(), + public_key: publicKey, + created_at: new Date().toISOString(), + }); + + const offerDetailsSet = await OfferDetailsSet.create({ + uuid: uuid.v7(), + offer_uuid: offerCreated.uuid, + wants: offerDetails.wants, + premium: offerDetails.premium, + trade_amount_eur: offerDetails.trade_amount_eur, + location_details: offerDetails.location_details, + time_availability_details: offerDetails.time_availability_details, + show_offer_to_trusted: offerDetails.show_offer_to_trusted, + show_offer_to_trusted_trusted: offerDetails.show_offer_to_trusted_trusted, + show_offer_to_all_members: offerDetails.show_offer_to_all_members, + is_onchain_accepted: offerDetails.is_onchain_accepted, + is_lightning_accepted: offerDetails.is_lightning_accepted, + are_big_notes_accepted: offerDetails.are_big_notes_accepted, + created_at: new Date().toISOString(), + }); } -module.exports = createOffer; +module.exports = { createOffer }; From ebd5f390e63ee6555659f44fe577039cb93f5435 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 13:56:26 +0100 Subject: [PATCH 064/239] ignore test results --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 43ddd2f..01f0e83 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,4 @@ dist .pnp.* -tests-results/ \ No newline at end of file +tests-results/* \ No newline at end of file From 62b3d73a819dc47876a33ebe01995aec8cc4b809 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 13:56:40 +0100 Subject: [PATCH 065/239] typo --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 01f0e83..ec88182 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,4 @@ dist .pnp.* -tests-results/* \ No newline at end of file +test-results/* \ No newline at end of file From 926dc3f3dd4e02a3c5a196bdcdccebfd1e0478ce Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 13:57:09 +0100 Subject: [PATCH 066/239] make sequelize STFU --- src/database.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/database.js b/src/database.js index b90f77f..133328f 100644 --- a/src/database.js +++ b/src/database.js @@ -10,6 +10,11 @@ const sequelize = new Sequelize({ database: process.env.POSTGRES_DB, username: process.env.POSTGRES_USER, password: process.env.POSTGRES_PASSWORD, + logging: (msg) => { + if (msg && (msg.includes('ERROR') || msg.includes('error'))) { + console.error(msg); + } + }, define: { timestamps: false, freezeTableName: true, From f00a3aa103c0273163ea82ebc2616aaca6c6e572 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 13:57:24 +0100 Subject: [PATCH 067/239] remove old dirt --- tests/app.spec.js | 8 -------- tests/createOffer.spec.js | 18 ++++++++++++++++ tests/test-setup.js | 43 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) delete mode 100644 tests/app.spec.js create mode 100644 tests/createOffer.spec.js create mode 100644 tests/test-setup.js diff --git a/tests/app.spec.js b/tests/app.spec.js deleted file mode 100644 index 9fdadf6..0000000 --- a/tests/app.spec.js +++ /dev/null @@ -1,8 +0,0 @@ -const { test, expect } = require('@playwright/test'); - -test('Home page has correct title', async ({ page }) => { - await page.goto('http://localhost/'); - const title = await page.title(); - - expect(title).toBe('Hello World'); -}); diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js new file mode 100644 index 0000000..06d68e7 --- /dev/null +++ b/tests/createOffer.spec.js @@ -0,0 +1,18 @@ +const { test, expect } = require('./test-setup'); +const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); +const NymSet = require('../src/models/NymSet'); +const ContactDetailsSet = require('../src/models/ContactDetailsSet'); + +test('Mock ecords are present', async ({ page }) => { + for (const someModel of [ + SessionRelatedToPublickey, + NymSet, + ContactDetailsSet, + ]) { + expect(await someModel.findOne()).toBeTruthy(); + } +}); + +// Check that UI controls are manageable with a few test cases. + +// Check that the offer is created after submitting diff --git a/tests/test-setup.js b/tests/test-setup.js new file mode 100644 index 0000000..186d166 --- /dev/null +++ b/tests/test-setup.js @@ -0,0 +1,43 @@ +import { test } from '@playwright/test'; +const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); +const NymSet = require('../src/models/NymSet'); +const ContactDetailsSet = require('../src/models/ContactDetailsSet'); + +test.beforeEach(async () => { + // Truncate all affected tables + + for (const someModel of [ + SessionRelatedToPublickey, + NymSet, + ContactDetailsSet, + ]) { + someModel.truncate(); + } + + await SessionRelatedToPublickey.create({ + uuid: '0195423b-f9ae-737e-98f3-880f6563ed8a', + session_uuid: '0195423c-33d7-75f8-921b-a06e6d3cb8c5', + public_key: + 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a', + created_at: new Date().toISOString(), + }); + + await NymSet.create({ + uuid: '01954240-ddbb-7d01-9017-efb3e500d333', + public_key: + 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a', + nym: 'test_nym', + created_at: new Date().toISOString(), + }); + + await ContactDetailsSet.create({ + uuid: '01954240-ddbb-7d01-9017-efb3e500d333', + public_key: + 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a', + encrypted_contact_details: + '+OD0/Y2IkJ99/E0KAJL/mp3kxQo4DFp1deSPnqiejlyGoeWzBiipemPVSTT/Jg/fCQbN9Pd/GJ6shxuwWECOVyB5PnMZOVJ1MPQ7I8A+63XZ0gKnSnJgry6F69f3MhEjH49JbeVJ37TbruFu/Woevo24VWz2gPXGBuyHLzeg1tyT9+7ZSygkcCrh+bchvymCoF1nNOm/UQKnwecH1wWzo8a+rNokazD1/3iey6iKmKewi+yGCgmljrB866akqBAl?iv=PAKhqTeBfYVX/muhM8xaEA==', + created_at: new Date().toISOString(), + }); +}); + +export { test, expect } from '@playwright/test'; From c7505f50093144e347cf022f494cffe389ccd500 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 14:20:53 +0100 Subject: [PATCH 068/239] add cookie to fixture --- tests/createOffer.spec.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js index 06d68e7..609cf71 100644 --- a/tests/createOffer.spec.js +++ b/tests/createOffer.spec.js @@ -1,9 +1,10 @@ const { test, expect } = require('./test-setup'); + const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); const NymSet = require('../src/models/NymSet'); const ContactDetailsSet = require('../src/models/ContactDetailsSet'); -test('Mock ecords are present', async ({ page }) => { +test('Mock records are present', async ({ page }) => { for (const someModel of [ SessionRelatedToPublickey, NymSet, @@ -13,6 +14,19 @@ test('Mock ecords are present', async ({ page }) => { } }); +test('Session cookie is there', async ({ page }) => { + await page.goto('http://localhost'); + + const cookiesInPage = await page.context().cookies(); + expect(cookiesInPage).toHaveLength(1); + expect(cookiesInPage[0].name).toBe('sessionUuid'); +}); + +test('Offers is reachable', async ({ page }) => { + await page.goto('http://localhost/offers'); + console.log(await page.innerHTML('html')); +}); + // Check that UI controls are manageable with a few test cases. // Check that the offer is created after submitting From a1e07724ce8881018407dd41fddfb44ef74beb62 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 14:21:40 +0100 Subject: [PATCH 069/239] add cookie to fixture --- tests/test-setup.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/test-setup.js b/tests/test-setup.js index 186d166..873667b 100644 --- a/tests/test-setup.js +++ b/tests/test-setup.js @@ -3,9 +3,7 @@ const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublick const NymSet = require('../src/models/NymSet'); const ContactDetailsSet = require('../src/models/ContactDetailsSet'); -test.beforeEach(async () => { - // Truncate all affected tables - +test.beforeEach(async ({ context }) => { for (const someModel of [ SessionRelatedToPublickey, NymSet, @@ -14,7 +12,7 @@ test.beforeEach(async () => { someModel.truncate(); } - await SessionRelatedToPublickey.create({ + const session = await SessionRelatedToPublickey.create({ uuid: '0195423b-f9ae-737e-98f3-880f6563ed8a', session_uuid: '0195423c-33d7-75f8-921b-a06e6d3cb8c5', public_key: @@ -38,6 +36,18 @@ test.beforeEach(async () => { '+OD0/Y2IkJ99/E0KAJL/mp3kxQo4DFp1deSPnqiejlyGoeWzBiipemPVSTT/Jg/fCQbN9Pd/GJ6shxuwWECOVyB5PnMZOVJ1MPQ7I8A+63XZ0gKnSnJgry6F69f3MhEjH49JbeVJ37TbruFu/Woevo24VWz2gPXGBuyHLzeg1tyT9+7ZSygkcCrh+bchvymCoF1nNOm/UQKnwecH1wWzo8a+rNokazD1/3iey6iKmKewi+yGCgmljrB866akqBAl?iv=PAKhqTeBfYVX/muhM8xaEA==', created_at: new Date().toISOString(), }); + + await context.addCookies([ + { + name: 'sessionUuid', + value: session.uuid, + domain: 'localhost', + path: '/', + httpOnly: true, + secure: false, + sameSite: 'None', + }, + ]); }); export { test, expect } from '@playwright/test'; From 1b5f22731800930cd028296b352e2ca8e2896780 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 17:22:37 +0100 Subject: [PATCH 070/239] finally some bloody test works --- tests/createOffer.spec.js | 27 ++++++++++++++++---------- tests/test-setup.js | 40 +++++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js index 609cf71..236610f 100644 --- a/tests/createOffer.spec.js +++ b/tests/createOffer.spec.js @@ -1,11 +1,15 @@ -const { test, expect } = require('./test-setup'); +const { test, expect, hardcodedSessionUuid } = require('./test-setup'); +const SessionCreated = require('../src/models/SessionCreated'); const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); const NymSet = require('../src/models/NymSet'); const ContactDetailsSet = require('../src/models/ContactDetailsSet'); +const sessionService = require('../src/services/sessionService'); + test('Mock records are present', async ({ page }) => { for (const someModel of [ + SessionCreated, SessionRelatedToPublickey, NymSet, ContactDetailsSet, @@ -14,19 +18,22 @@ test('Mock records are present', async ({ page }) => { } }); -test('Session cookie is there', async ({ page }) => { - await page.goto('http://localhost'); - +test('Hardcoded session cookie is there', async ({ context }) => { + const page = await context.newPage(); const cookiesInPage = await page.context().cookies(); expect(cookiesInPage).toHaveLength(1); expect(cookiesInPage[0].name).toBe('sessionUuid'); + expect(cookiesInPage[0].value).toBe(hardcodedSessionUuid); }); -test('Offers is reachable', async ({ page }) => { +test('Offers is reachable', async ({ context }) => { + const page = await context.newPage(); + const cookiez = await page.context().cookies(); + + await page.pause(); await page.goto('http://localhost/offers'); - console.log(await page.innerHTML('html')); + + const createOfferButton = page.locator('#button-start-create-offer'); + await expect(createOfferButton).toBeVisible(); + await expect(createOfferButton).toContainText('Crear nueva oferta'); }); - -// Check that UI controls are manageable with a few test cases. - -// Check that the offer is created after submitting diff --git a/tests/test-setup.js b/tests/test-setup.js index 873667b..5e9c7be 100644 --- a/tests/test-setup.js +++ b/tests/test-setup.js @@ -1,10 +1,17 @@ import { test } from '@playwright/test'; + +const SessionCreated = require('../src/models/SessionCreated'); const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); const NymSet = require('../src/models/NymSet'); const ContactDetailsSet = require('../src/models/ContactDetailsSet'); +const hardcodedSessionUuid = '0195423c-33d7-75f8-921b-a06e6d3cb8c5'; +const hardcodedPublicKey = + 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a'; + test.beforeEach(async ({ context }) => { for (const someModel of [ + SessionCreated, SessionRelatedToPublickey, NymSet, ContactDetailsSet, @@ -12,42 +19,55 @@ test.beforeEach(async ({ context }) => { someModel.truncate(); } - const session = await SessionRelatedToPublickey.create({ + const currentTimestamp = new Date(); + const expiryTimestamp = new Date(currentTimestamp.getTime()); + expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + 60); + await SessionCreated.create({ + uuid: hardcodedSessionUuid, + created_at: currentTimestamp.toISOString(), + expires_at: expiryTimestamp.toISOString(), + }); + + await SessionRelatedToPublickey.create({ uuid: '0195423b-f9ae-737e-98f3-880f6563ed8a', - session_uuid: '0195423c-33d7-75f8-921b-a06e6d3cb8c5', - public_key: - 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a', + session_uuid: hardcodedSessionUuid, + public_key: hardcodedPublicKey, created_at: new Date().toISOString(), }); await NymSet.create({ uuid: '01954240-ddbb-7d01-9017-efb3e500d333', - public_key: - 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a', + public_key: hardcodedPublicKey, nym: 'test_nym', created_at: new Date().toISOString(), }); await ContactDetailsSet.create({ uuid: '01954240-ddbb-7d01-9017-efb3e500d333', - public_key: - 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a', + public_key: hardcodedPublicKey, encrypted_contact_details: '+OD0/Y2IkJ99/E0KAJL/mp3kxQo4DFp1deSPnqiejlyGoeWzBiipemPVSTT/Jg/fCQbN9Pd/GJ6shxuwWECOVyB5PnMZOVJ1MPQ7I8A+63XZ0gKnSnJgry6F69f3MhEjH49JbeVJ37TbruFu/Woevo24VWz2gPXGBuyHLzeg1tyT9+7ZSygkcCrh+bchvymCoF1nNOm/UQKnwecH1wWzo8a+rNokazD1/3iey6iKmKewi+yGCgmljrB866akqBAl?iv=PAKhqTeBfYVX/muhM8xaEA==', created_at: new Date().toISOString(), }); +}); +test.beforeEach(async ({ context }) => { await context.addCookies([ { name: 'sessionUuid', - value: session.uuid, + value: hardcodedSessionUuid, domain: 'localhost', path: '/', + expires: Math.floor( + new Date(new Date().setMonth(new Date().getMonth() + 1)).getTime() / + 1000 + ), //This monster is this day next month, turned into epoch format httpOnly: true, secure: false, - sameSite: 'None', + sameSite: 'Lax', }, ]); }); export { test, expect } from '@playwright/test'; +export { hardcodedSessionUuid }; From 95c02cf7e2b65d6b84304938d3e4bafb5feea5be Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 23:55:52 +0100 Subject: [PATCH 071/239] project fluff --- .gitignore | 8 +++++++- package-lock.json | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ec88182..846e04f 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,10 @@ dist .pnp.* -test-results/* \ No newline at end of file +test-results/* + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index 5490e56..1c6f215 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "express-hello-world", - "version": "1.0.0", + "name": "laseca", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "express-hello-world", - "version": "1.0.0", + "name": "laseca", + "version": "0.1.0", "license": "ISC", "dependencies": { "commander": "^13.1.0", From 3ac74258245b17415c1dd5d248bf9c3ecf357799 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 23:56:06 +0100 Subject: [PATCH 072/239] remove unused declaration --- src/services/offerService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/offerService.js b/src/services/offerService.js index cca3b39..56ef780 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -10,7 +10,7 @@ async function createOffer(publicKey, offerDetails) { created_at: new Date().toISOString(), }); - const offerDetailsSet = await OfferDetailsSet.create({ + await OfferDetailsSet.create({ uuid: uuid.v7(), offer_uuid: offerCreated.uuid, wants: offerDetails.wants, From c1161e3a6634a952aa92d9596e61ee03a76c36be Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 26 Feb 2025 23:56:14 +0100 Subject: [PATCH 073/239] cool tests!!! --- tests/createOffer.spec.js | 58 ++++++++++++++++++++++++++++++++---- tests/recorderHelper.spec.js | 27 +++++++++++++++++ tests/test-setup.js | 7 ++--- 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 tests/recorderHelper.spec.js diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js index 236610f..a64c0de 100644 --- a/tests/createOffer.spec.js +++ b/tests/createOffer.spec.js @@ -5,9 +5,7 @@ const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublick const NymSet = require('../src/models/NymSet'); const ContactDetailsSet = require('../src/models/ContactDetailsSet'); -const sessionService = require('../src/services/sessionService'); - -test('Mock records are present', async ({ page }) => { +test('Mock records are present', async () => { for (const someModel of [ SessionCreated, SessionRelatedToPublickey, @@ -28,12 +26,60 @@ test('Hardcoded session cookie is there', async ({ context }) => { test('Offers is reachable', async ({ context }) => { const page = await context.newPage(); - const cookiez = await page.context().cookies(); - await page.pause(); await page.goto('http://localhost/offers'); - const createOfferButton = page.locator('#button-start-create-offer'); await expect(createOfferButton).toBeVisible(); await expect(createOfferButton).toContainText('Crear nueva oferta'); }); + +test('Create an offer with a few options creates in DB', async ({ + context, +}) => { + const page = await context.newPage(); + await page.goto('http://localhost/offers'); + + await page.getByRole('button', { name: 'Crear nueva oferta' }).click(); + await page.getByRole('button', { name: 'Quiero vender Bitcoin' }).click(); + await page.getByRole('button', { name: 'Quiero comprar Bitcoin' }).click(); + await page.getByRole('button', { name: '+' }).click(); + await page.getByRole('button', { name: '+' }).click(); + await page.getByRole('button', { name: '+' }).click(); + await page.getByRole('button', { name: '-' }).click(); + await expect(page.locator('#premium-value')).toContainText('2%'); + await page.locator('#input-eur-amount').click(); + await page.locator('#input-eur-amount').press('ControlOrMeta+a'); + await page.locator('#input-eur-amount').fill('50'); + await expect(page.locator('#input-eur-amount')).toHaveValue('50'); + await page + .getByText( + 'Añade los detalles de tu oferta Quiero comprar Bitcoin Quiero vender Bitcoin' + ) + .click(); + await page + .getByRole('textbox', { name: '¿Dónde? Ej."Eixample", "La' }) + .click(); + await page + .getByRole('textbox', { name: '¿Dónde? Ej."Eixample", "La' }) + .fill('En algún lugar'); + await page + .getByRole('textbox', { name: '¿Cuándo? Ej."Cualquier hora' }) + .click(); + await page + .getByRole('textbox', { name: '¿Cuándo? Ej."Cualquier hora' }) + .fill('En algún momento'); + await page.locator('#onchain-checkbox').uncheck(); + await expect(page.locator('#onchain-checkbox')).not.toBeChecked(); + await expect(page.locator('#lightning-checkbox')).toBeChecked(); + await page.locator('#input-eur-amount').click(); + await page.locator('#my-trusted-trusted-checkbox').uncheck(); + await page.locator('#all-members-checkbox').check(); + await page.locator('#my-trusted-trusted-checkbox').check(); + await page.locator('#all-members-checkbox').uncheck(); + await page.locator('#large-bills-checkbox').check(); + await page.getByRole('button', { name: 'Publicar oferta' }).click(); + await page.locator('#close-offer-controls-x').click(); + await expect(page.locator('#offers-root')).toMatchAriaSnapshot( + `- button "Crear nueva oferta"` + ); +}); diff --git a/tests/recorderHelper.spec.js b/tests/recorderHelper.spec.js new file mode 100644 index 0000000..2c87c21 --- /dev/null +++ b/tests/recorderHelper.spec.js @@ -0,0 +1,27 @@ +// You can uncomment this below to open a recorder page + +/* +const { chromium } = require('playwright'); +test('Mock records are present', async () => { + const browser = await chromium.launch({ headless: false }); + const context = await browser.newContext(); + await context.addCookies([ + { + name: 'sessionUuid', + value: hardcodedSessionUuid, + domain: 'localhost', + path: '/', + expires: Math.floor( + new Date(new Date().setMonth(new Date().getMonth() + 1)).getTime() / + 1000 + ), //This monster is this day next month, turned into epoch format + httpOnly: true, + secure: false, + sameSite: 'Lax', + }, + ]); + + const page = await context.newPage(); + await page.goto('http://localhost'); +}); +*/ diff --git a/tests/test-setup.js b/tests/test-setup.js index 5e9c7be..3d4b669 100644 --- a/tests/test-setup.js +++ b/tests/test-setup.js @@ -1,4 +1,4 @@ -import { test } from '@playwright/test'; +const { test, expect } = require('@playwright/test'); const SessionCreated = require('../src/models/SessionCreated'); const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); @@ -9,7 +9,7 @@ const hardcodedSessionUuid = '0195423c-33d7-75f8-921b-a06e6d3cb8c5'; const hardcodedPublicKey = 'd3d4c49e7bdbbbf3082151add080e92f9a458d5dec993b371fe6d02cd394d57a'; -test.beforeEach(async ({ context }) => { +test.beforeEach(async () => { for (const someModel of [ SessionCreated, SessionRelatedToPublickey, @@ -69,5 +69,4 @@ test.beforeEach(async ({ context }) => { ]); }); -export { test, expect } from '@playwright/test'; -export { hardcodedSessionUuid }; +module.exports = { test, expect, hardcodedSessionUuid }; From 7dbf1fd082e0684723ae1f2bcd3e2bfe403ea22e Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 12:24:17 +0100 Subject: [PATCH 074/239] very complete test yes sir --- tests/createOffer.spec.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js index a64c0de..38aa863 100644 --- a/tests/createOffer.spec.js +++ b/tests/createOffer.spec.js @@ -4,6 +4,7 @@ const SessionCreated = require('../src/models/SessionCreated'); const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); const NymSet = require('../src/models/NymSet'); const ContactDetailsSet = require('../src/models/ContactDetailsSet'); +const OfferDetailsSet = require('../src/models/OfferDetailsSet'); test('Mock records are present', async () => { for (const someModel of [ @@ -71,15 +72,32 @@ test('Create an offer with a few options creates in DB', async ({ await page.locator('#onchain-checkbox').uncheck(); await expect(page.locator('#onchain-checkbox')).not.toBeChecked(); await expect(page.locator('#lightning-checkbox')).toBeChecked(); - await page.locator('#input-eur-amount').click(); await page.locator('#my-trusted-trusted-checkbox').uncheck(); await page.locator('#all-members-checkbox').check(); await page.locator('#my-trusted-trusted-checkbox').check(); + await expect(page.locator('#my-trusted-trusted-checkbox')).toBeChecked(); await page.locator('#all-members-checkbox').uncheck(); + await expect(page.locator('#all-members-checkbox')).not.toBeChecked(); await page.locator('#large-bills-checkbox').check(); + await expect(page.locator('#large-bills-checkbox')).toBeChecked(); await page.getByRole('button', { name: 'Publicar oferta' }).click(); await page.locator('#close-offer-controls-x').click(); await expect(page.locator('#offers-root')).toMatchAriaSnapshot( `- button "Crear nueva oferta"` ); + + const createdOfferDetailsSetRecord = await OfferDetailsSet.findOne(); + expect(createdOfferDetailsSetRecord.wants).toBe('BTC'); + expect(createdOfferDetailsSetRecord.premium).toBe('0.02'); + expect(createdOfferDetailsSetRecord.trade_amount_eur).toBe(50); + expect(createdOfferDetailsSetRecord.location_details).toBe('En algún lugar'); + expect(createdOfferDetailsSetRecord.time_availability_details).toBe( + 'En algún momento' + ); + expect(createdOfferDetailsSetRecord.show_offer_to_trusted).toBe(true); + expect(createdOfferDetailsSetRecord.show_offer_to_trusted_trusted).toBe(true); + expect(createdOfferDetailsSetRecord.show_offer_to_all_members).toBe(false); + expect(createdOfferDetailsSetRecord.is_onchain_accepted).toBe(false); + expect(createdOfferDetailsSetRecord.is_lightning_accepted).toBe(true); + expect(createdOfferDetailsSetRecord.are_big_notes_accepted).toBe(true); }); From e632bbf0e036dfde171c473e5144d8b48a6fd17a Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 12:25:51 +0100 Subject: [PATCH 075/239] remvoe old file --- src/views/createOffer.ejs | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 src/views/createOffer.ejs diff --git a/src/views/createOffer.ejs b/src/views/createOffer.ejs deleted file mode 100644 index 3ea5150..0000000 --- a/src/views/createOffer.ejs +++ /dev/null @@ -1,25 +0,0 @@ - - - - Seca home - - - - - - -
-

la seca

- - - - -
-
- -
- - - From 35d41827c6bdff0869ec8acfcad4b93582f45e3d Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 12:36:22 +0100 Subject: [PATCH 076/239] a couple of small refactors --- src/views/offers.ejs | 3 +-- src/views/partials/appCommonScripts.ejs | 1 + src/views/partials/commonStyles.ejs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/views/partials/commonStyles.ejs diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 7bed996..0a49278 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -4,7 +4,7 @@ Seca home - + <%- include("partials/commonStyles") %> @@ -179,7 +179,6 @@
<%- include("partials/appCommonScripts") %> - diff --git a/src/views/partials/appCommonScripts.ejs b/src/views/partials/appCommonScripts.ejs index 1df2d83..58c2ce7 100644 --- a/src/views/partials/appCommonScripts.ejs +++ b/src/views/partials/appCommonScripts.ejs @@ -1 +1,2 @@ + \ No newline at end of file diff --git a/src/views/partials/commonStyles.ejs b/src/views/partials/commonStyles.ejs new file mode 100644 index 0000000..30c6282 --- /dev/null +++ b/src/views/partials/commonStyles.ejs @@ -0,0 +1 @@ + \ No newline at end of file From 0c038cb03ffddbfc6e2b0206ec4e1efe68744628 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 12:51:22 +0100 Subject: [PATCH 077/239] format --- src/views/partials/appCommonScripts.ejs | 2 +- src/views/partials/commonStyles.ejs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/partials/appCommonScripts.ejs b/src/views/partials/appCommonScripts.ejs index 58c2ce7..9c2482c 100644 --- a/src/views/partials/appCommonScripts.ejs +++ b/src/views/partials/appCommonScripts.ejs @@ -1,2 +1,2 @@ - \ No newline at end of file + diff --git a/src/views/partials/commonStyles.ejs b/src/views/partials/commonStyles.ejs index 30c6282..f3843f5 100644 --- a/src/views/partials/commonStyles.ejs +++ b/src/views/partials/commonStyles.ejs @@ -1 +1 @@ - \ No newline at end of file + From aebfad80d536fcee0277c6e97eb4606c8e5883b9 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 12:51:32 +0100 Subject: [PATCH 078/239] refactor steps styles --- src/public/css/offers.css | 19 +++++++++---------- src/views/offers.ejs | 34 ++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 16fad27..e78855f 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -19,7 +19,7 @@ width: 50%; } - #create-offer-controls > * { + .create-offer-step { width: 100%; } @@ -51,7 +51,7 @@ width: 33%; } - #create-offer-controls > * { + .create-offer-step { margin-left: auto; margin-right: auto; width: 70%; @@ -67,10 +67,12 @@ } } -#create-offer-controls > * { +#create-offer-controls { + text-align: center; +} +.create-offer-step { text-align: center; } - #close-offer-controls-area { display: flex; justify-content: end; @@ -108,6 +110,9 @@ .premium-button { background: white; border: 2px solid #e1c300; + width: 100%; + height: 50%; + font-size: 1em; } .premium-button:hover { @@ -122,12 +127,6 @@ border-bottom-right-radius: 10px; } -.premium-button { - width: 100%; - height: 50%; - font-size: 1em; -} - #premium-price-display-area { margin-left: 0; margin-right: auto; diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 0a49278..da43ce1 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -29,18 +29,20 @@ />

Añade los detalles de tu oferta

-
- +
+
+ +
-
+

Premium

@@ -63,7 +65,7 @@
-
+

¿Cuánto?

@@ -91,7 +93,7 @@
-
+

¿Dónde y cuándo?

-
+

¿Cómo se mueve el Bitcoin?

-
+

¿Quién puede ver la oferta?

-
+

Extras

Date: Thu, 27 Feb 2025 13:02:54 +0100 Subject: [PATCH 079/239] pretty boxes --- src/public/css/offers.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index e78855f..b9b32eb 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -72,7 +72,17 @@ } .create-offer-step { text-align: center; + border-radius: 20px; + box-shadow: 0 0 13px #ccc; + padding: 20px 0; + margin-top: 20px; + margin-bottom: 20px; } + +.create-offer-step h3 { + margin-top: 0; +} + #close-offer-controls-area { display: flex; justify-content: end; From ab9d502945443ffb98e2cf1a9cd2d2f088914f84 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 16:54:05 +0100 Subject: [PATCH 080/239] confirmation popup --- src/public/css/seca.css | 40 ++++++++++++++++++++++++++- src/public/img/circle-check-green.svg | 40 +++++++++++++++++++++++++++ src/public/img/circle-check-white.svg | 40 +++++++++++++++++++++++++++ src/views/offers.ejs | 6 ++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/public/img/circle-check-green.svg create mode 100644 src/public/img/circle-check-white.svg diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 629ba1a..d928f4b 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -14,9 +14,14 @@ .button-medium { height: 3em; - padding: 1em; + padding: 0.5em 1em; border-radius: 10px; } + + .top-notification-good { + top: 25px; + + } } @media (min-width: 769px) { @@ -33,8 +38,41 @@ padding: 1em; border-radius: 10px; } + + .top-notification-good { + top: 50px; + } } +.top-notification-good { + border-radius: 10px; + background-color: rgb(9, 165, 9); + color: white; + font-weight: 800; + opacity: 0.8; + position: fixed; + padding: 20px; + display: flex; + justify-content: space-evenly; + align-items: center; + left: 50%; + transform: translateX(-50%); +} +.top-notification-good > * { + margin: 0 20px; +} +.top-notification-good > *:first-child { + margin-left: 0; +} +.top-notification-good > *:last-child { + margin-right: 0; +} + +.top-notification-good img { + height: 50px; +} + + .badges { display: grid; grid-template-columns: repeat(2, 1fr); diff --git a/src/public/img/circle-check-green.svg b/src/public/img/circle-check-green.svg new file mode 100644 index 0000000..75a0c96 --- /dev/null +++ b/src/public/img/circle-check-green.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/public/img/circle-check-white.svg b/src/public/img/circle-check-white.svg new file mode 100644 index 0000000..e5427dc --- /dev/null +++ b/src/public/img/circle-check-white.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/src/views/offers.ejs b/src/views/offers.ejs index da43ce1..241ba7d 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -178,6 +178,12 @@
+
+ +

¡Oferta creada! Puedes verla en tus ofertas.

+
<%- include("partials/appCommonScripts") %> From 0b3fe23cd371548cb76f6665334955e192e2422a Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 17:36:22 +0100 Subject: [PATCH 081/239] pop up moves --- src/public/css/seca.css | 8 +++++++- src/public/javascript/offers.js | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index d928f4b..f847728 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -56,7 +56,13 @@ justify-content: space-evenly; align-items: center; left: 50%; - transform: translateX(-50%); + transform: translate(-50%, -200%); + transition: transform 1s ease-in-out; + +} + +.top-notification-good.revealed { + transform: translate(-50%, 0%); } .top-notification-good > * { margin: 0 20px; diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 3a73d12..3f15f18 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -43,6 +43,8 @@ const bigNotesAcceptedCheckbox = document.getElementById( const publishOfferButton = document.getElementById('button-submit-offer'); +const offerCreatedPopup = document.getElementById('offer-created-confirmation'); + function toggleCreateOfferControls() { createOfferControls.style.display = createOfferControls.style.display === 'block' ? 'none' : 'block'; @@ -154,6 +156,14 @@ async function publishOffer() { }, body: JSON.stringify({ offerDetails }), }); + + offerCreatedPopup.classList.add('revealed'); + setTimeout(() => { + offerCreatedPopup.classList.remove('revealed'); + }, 3000); + setTimeout(() => { + offerCreatedPopup.classList.remove('revealed'); + }, 3000); } buttonStartCreateOffer.addEventListener('click', () => { From aec803d321ba429f4bb6ab9bef6c7d9bea5c0fb5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 18:26:36 +0100 Subject: [PATCH 082/239] fix tests --- src/public/css/seca.css | 12 +++++++++--- src/public/javascript/offers.js | 7 +++++-- src/views/offers.ejs | 2 +- tests/createOffer.spec.js | 12 ++++++++---- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index f847728..27ced1a 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -44,6 +44,12 @@ } } + +.max-size-zero { + max-width: 0; + max-height: 0; +} + .top-notification-good { border-radius: 10px; background-color: rgb(9, 165, 9); @@ -56,9 +62,9 @@ justify-content: space-evenly; align-items: center; left: 50%; - transform: translate(-50%, -200%); - transition: transform 1s ease-in-out; - + transform: translate(-50%, -200px); + transition: + transform 2s ease-in-out; } .top-notification-good.revealed { diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 3f15f18..054d7fa 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -157,13 +157,16 @@ async function publishOffer() { body: JSON.stringify({ offerDetails }), }); + offerCreatedPopup.classList.remove('max-size-zero'); offerCreatedPopup.classList.add('revealed'); setTimeout(() => { offerCreatedPopup.classList.remove('revealed'); }, 3000); setTimeout(() => { - offerCreatedPopup.classList.remove('revealed'); - }, 3000); + offerCreatedPopup.classList.add('max-size-zero'); + }, 4000); + + toggleCreateOfferControls(); } buttonStartCreateOffer.addEventListener('click', () => { diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 241ba7d..6737f47 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -178,7 +178,7 @@
-
+
diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js index 38aa863..1de4566 100644 --- a/tests/createOffer.spec.js +++ b/tests/createOffer.spec.js @@ -1,4 +1,5 @@ const { test, expect, hardcodedSessionUuid } = require('./test-setup'); +const { chromium } = require('playwright'); const SessionCreated = require('../src/models/SessionCreated'); const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); @@ -41,6 +42,7 @@ test('Create an offer with a few options creates in DB', async ({ await page.goto('http://localhost/offers'); await page.getByRole('button', { name: 'Crear nueva oferta' }).click(); + await expect(page.locator('#close-offer-controls-area')).toBeVisible(); await page.getByRole('button', { name: 'Quiero vender Bitcoin' }).click(); await page.getByRole('button', { name: 'Quiero comprar Bitcoin' }).click(); await page.getByRole('button', { name: '+' }).click(); @@ -80,11 +82,13 @@ test('Create an offer with a few options creates in DB', async ({ await expect(page.locator('#all-members-checkbox')).not.toBeChecked(); await page.locator('#large-bills-checkbox').check(); await expect(page.locator('#large-bills-checkbox')).toBeChecked(); + await page.getByRole('button', { name: 'Publicar oferta' }).click(); - await page.locator('#close-offer-controls-x').click(); - await expect(page.locator('#offers-root')).toMatchAriaSnapshot( - `- button "Crear nueva oferta"` - ); + await expect(page.locator('#offer-created-confirmation')).toBeInViewport(); + await expect( + page.locator('#offer-created-confirmation') + ).not.toBeInViewport(); + await expect(page.locator('#close-offer-controls-area')).not.toBeVisible(); const createdOfferDetailsSetRecord = await OfferDetailsSet.findOne(); expect(createdOfferDetailsSetRecord.wants).toBe('BTC'); From 94c2231a57b70fd816f65fa4fc67cd5b210cc308 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 18:38:30 +0100 Subject: [PATCH 083/239] move div out --- src/views/offers.ejs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 6737f47..053a8f7 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -178,12 +178,12 @@
-
- -

¡Oferta creada! Puedes verla en tus ofertas.

-
+
+
+ +

¡Oferta creada! Puedes verla en tus ofertas.

<%- include("partials/appCommonScripts") %> From 7e8b27b49d771ae5e9ae88176e50581379cb6b98 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 18:38:42 +0100 Subject: [PATCH 084/239] show confirmation popup --- src/public/css/seca.css | 4 ++-- src/public/javascript/invite.js | 14 ++++++++++---- src/views/invite.ejs | 11 ++++++----- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 27ced1a..255b284 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -62,7 +62,7 @@ justify-content: space-evenly; align-items: center; left: 50%; - transform: translate(-50%, -200px); + transform: translate(-50%, -500px); transition: transform 2s ease-in-out; } @@ -218,7 +218,7 @@ h1 { padding: 0 10%; } -.invite-card-content * { +.invite-card-content > * { margin: 1vh auto; text-align: center; } diff --git a/src/public/javascript/invite.js b/src/public/javascript/invite.js index 04ef5c8..70bb66d 100644 --- a/src/public/javascript/invite.js +++ b/src/public/javascript/invite.js @@ -8,6 +8,15 @@ window.onload = function () { } }; +const signUpConfirmation = document.querySelector('#sign-up-success'); + +function showConfirmationAndRedirect() { + signUpConfirmation.classList.add('revealed'); + setTimeout(() => { + window.location.href = '/createProfile'; + }, 5000); +} + async function acceptInvite() { let challengeResponse; try { @@ -60,9 +69,6 @@ async function acceptInvite() { } if (verifyResponse.ok) { - document.querySelector('#sign-up-success').style.display = 'block'; - setTimeout(() => { - window.location.href = '/createProfile'; - }, 1000); + showConfirmationAndRedirect(); } } diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 0cd5df1..cce211c 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -4,7 +4,6 @@ Invite Details - @@ -89,11 +88,12 @@

¿No tienes cuenta de Nostr?

@@ -107,5 +107,6 @@

+ From 7f84889968938b42900c910922c9cd361e19ce7d Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 18:40:15 +0100 Subject: [PATCH 085/239] formatting --- src/public/css/invite.css | 0 src/public/css/seca.css | 6 +----- src/views/invite.ejs | 17 ++++++++--------- src/views/offers.ejs | 9 +++++---- tests/createOffer.spec.js | 1 - 5 files changed, 14 insertions(+), 19 deletions(-) create mode 100644 src/public/css/invite.css diff --git a/src/public/css/invite.css b/src/public/css/invite.css new file mode 100644 index 0000000..e69de29 diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 255b284..13cd9ba 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -20,7 +20,6 @@ .top-notification-good { top: 25px; - } } @@ -44,7 +43,6 @@ } } - .max-size-zero { max-width: 0; max-height: 0; @@ -63,8 +61,7 @@ align-items: center; left: 50%; transform: translate(-50%, -500px); - transition: - transform 2s ease-in-out; + transition: transform 2s ease-in-out; } .top-notification-good.revealed { @@ -84,7 +81,6 @@ height: 50px; } - .badges { display: grid; grid-template-columns: repeat(2, 1fr); diff --git a/src/views/invite.ejs b/src/views/invite.ejs index cce211c..2750db7 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -4,7 +4,8 @@ Invite Details - + <%- include("partials/commonStyles") %> + @@ -86,14 +87,12 @@ >

-
- -

¡Bien! Hemos dado de alta tu clave de Nostr. Te vamos a redirigir a la seca, espera un momento.

+
+ +

+ ¡Bien! Hemos dado de alta tu clave de Nostr. Te vamos a redirigir + a la seca, espera un momento. +

¿No tienes cuenta de Nostr?

diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 053a8f7..223a408 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -179,10 +179,11 @@

-
- +
+

¡Oferta creada! Puedes verla en tus ofertas.

diff --git a/tests/createOffer.spec.js b/tests/createOffer.spec.js index 1de4566..2820b3a 100644 --- a/tests/createOffer.spec.js +++ b/tests/createOffer.spec.js @@ -1,5 +1,4 @@ const { test, expect, hardcodedSessionUuid } = require('./test-setup'); -const { chromium } = require('playwright'); const SessionCreated = require('../src/models/SessionCreated'); const SessionRelatedToPublickey = require('../src/models/SessionRelatedToPublickey'); From c6e48af792d39086137c82d25db691b6e44c12bf Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 27 Feb 2025 19:00:36 +0100 Subject: [PATCH 086/239] cool card --- src/public/css/invite.css | 39 +++++++++++++++++++++++++++++++++++++++ src/public/css/seca.css | 29 +++++------------------------ src/views/invite.ejs | 8 ++++++-- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/public/css/invite.css b/src/public/css/invite.css index e69de29..6e3d2f3 100644 --- a/src/public/css/invite.css +++ b/src/public/css/invite.css @@ -0,0 +1,39 @@ +@media (max-width: 768px) { + #nostr-signup-button { + width: 200px; + } + + #invite-card-content { + width:100%; + } +} + +@media (min-width: 769px) { + #nostr-signup-button { + width: 300px; + } + + #invite-card-content { + width: 40%; + min-width: min-content; + } +} + +#laseca-logo { + width: 200px; +} + +#nostr-signup-button p { + font-size: 1.5rem; +} + +#invite-card-content { + margin-right: auto; + margin-left: auto; + padding: 20px; +} + +#invite-card-content > * { + margin: 1vh auto; + text-align: center; +} diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 13cd9ba..46ce85a 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -157,6 +157,10 @@ h1 { margin: 1%; } +.shadowed-round-area { + border-radius: 20px; + box-shadow: 0 0 13px #ccc; +} #login-card > * { text-align: center; margin-right: auto; @@ -204,21 +208,6 @@ h1 { padding: 0.5em; } -.invite-card { - margin: 5vh 20vw; - height: 100%; -} - -.invite-card-content { - margin: 3% 5%; - padding: 0 10%; -} - -.invite-card-content > * { - margin: 1vh auto; - text-align: center; -} - .card-secondary { background-color: #369; border-radius: 1vw; @@ -240,6 +229,7 @@ h1 { color: white; padding: 1%; cursor: pointer; + border: 0; } .button-nostr:hover { @@ -251,12 +241,3 @@ h1 { cursor: default; border: 0; } - -#nostr-signup-button { - width: 20vw; - border: 0; -} - -#nostr-signup-button p { - font-size: 1.5rem; -} diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 2750db7..3957b59 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -11,10 +11,14 @@
-
+

¡Has sido invitado a la seca!

- +

Usa tu extensión de Nostr para darte de alta:

From dff090a9d4265fe1cc51bd9a673c7bb38fdf1cd1 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 28 Feb 2025 12:36:04 +0100 Subject: [PATCH 087/239] refactored and restyled --- src/public/css/createProfile.css | 62 ++++++++++++++++++++++++++++ src/public/css/seca.css | 71 +++++++++++--------------------- src/views/createProfile.ejs | 31 ++++---------- 3 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 src/public/css/createProfile.css diff --git a/src/public/css/createProfile.css b/src/public/css/createProfile.css new file mode 100644 index 0000000..f37d2cf --- /dev/null +++ b/src/public/css/createProfile.css @@ -0,0 +1,62 @@ + +@media (max-width: 768px) {.over-background > *:first-child { + margin-bottom: 5px; +} + +.over-background > *:last-child { + margin-top: 5px; +} + +.over-background > * { + margin-bottom: 5px; + margin-top: 5px; +}} + + +@media (min-width: 769px) {.over-background > *:first-child { + margin-bottom: 10px; +} + +.over-background > *:last-child { + margin-top: 10px; +} + +.over-background > * { + margin-bottom: 10px; + margin-top: 10px; +}} + +.badges { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(3, 1fr); +} + +.badge { + border: 2px solid #e1c300; + border-radius: 10px; + padding: 2%; + margin: 1%; +} + +.badge input { + max-width: 80%; +} + +.create-profile-card-section > * { + text-align: center; + margin-bottom: 10px; + margin-right: auto; + margin-left: auto; + display: block; + max-width: 1000px; +} + +.create-profile-card-section .badge img { + width: 10%; +} + +h2 { + margin-top: 0; + margin-bottom: 5px; +} \ No newline at end of file diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 46ce85a..ddc68ba 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -43,6 +43,30 @@ } } +body { + background-color: #bdd5ea; +} + +h1 { + color: #e1c300; +} + +.clickable { + cursor: pointer; +} + +.money-input { + text-align: end; +} + +.input-is-valid { + border: 1px solid green; +} + +.input-is-invalid { + border: 2px solid red; +} + .max-size-zero { max-width: 0; max-height: 0; @@ -81,42 +105,6 @@ height: 50px; } -.badges { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(3, 1fr); -} - -.badge { - border: 2px solid #e1c300; - border-radius: 10px; - padding: 2%; - margin: 2%; -} - -body { - background-color: #bdd5ea; -} - -h1 { - color: #e1c300; -} - -.clickable { - cursor: pointer; -} - -.money-input { - text-align: end; -} - -.input-is-valid { - border: 1px solid green; -} - -.input-is-invalid { - border: 2px solid red; -} .button-group button { border: 0; @@ -160,6 +148,7 @@ h1 { .shadowed-round-area { border-radius: 20px; box-shadow: 0 0 13px #ccc; + padding: 20px; } #login-card > * { text-align: center; @@ -169,17 +158,7 @@ h1 { margin-bottom: 1vh; } -.create-profile-card-section > * { - text-align: center; - margin-right: auto; - margin-left: auto; - display: block; - max-width: 1000px; -} -.create-profile-card-section .badge img { - width: 10%; -} .text-warning { color: red; diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 87b57ef..a9cd8b6 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -4,21 +4,22 @@ Crear perfil - + <%- include("partials/commonStyles") %> +
+

Crea tu perfil

Tu clave de Nostr ya es parte de la seca.

Añade detalles a tu perfil para poder empezar a comerciar.

-
+
-

Perfil

-
-
-

Datos de contacto

+

Métodos de contacto

-

- Añade métodos de contacto para poder hablar con otros miembros. - Debes añadir al menos uno. Siempre puedes editarlos más adelante. -

- - -

Tus métodos de contacto son necesarios para hablar con otros @@ -61,9 +46,10 @@

Tus métodos de contacto se guardan encriptados con tu clave de - nostr. La seca no puede verlos. + nostr. laseca nunca puede verlos. Solo los miembros con los que + tú decidas podrán verlos.

-

Solo los miembros con los que tú decidas podrán verlos.

+

Debes añadir al menos un método de contacto para completar tu perfil.

@@ -128,6 +114,7 @@
+
From 12bc0d2b849ec1f6d34f8d3145d201b3e5465a97 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 28 Feb 2025 12:42:13 +0100 Subject: [PATCH 088/239] pop up confirmation for create profile --- src/public/css/seca.css | 2 +- src/public/javascript/createProfile.js | 7 ++++++- src/views/createProfile.ejs | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/public/css/seca.css b/src/public/css/seca.css index ddc68ba..e23a1bd 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -85,7 +85,7 @@ h1 { align-items: center; left: 50%; transform: translate(-50%, -500px); - transition: transform 2s ease-in-out; + transition: transform 1s ease-in-out; } .top-notification-good.revealed { diff --git a/src/public/javascript/createProfile.js b/src/public/javascript/createProfile.js index f7af1f1..4be8920 100644 --- a/src/public/javascript/createProfile.js +++ b/src/public/javascript/createProfile.js @@ -1,3 +1,7 @@ +const createProfileConfirmation = document.querySelector( + '#create-profile-success' +); + function debounce(func, wait) { let timeout; return function (...args) { @@ -59,9 +63,10 @@ async function createProfile(allInputs) { body: JSON.stringify({ nym }), }); + createProfileConfirmation.classList.add('revealed'); setTimeout(() => { window.location.href = '/home'; - }, 1000); + }, 5000); } function onLoadErrands(allInputs, submitProfileButton) { diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index a9cd8b6..74381f8 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -114,6 +114,13 @@
+ +
+
+ +

+ ¡Bien! Tu perfil está completo. Te estamos llevando a la aplicación... +

From b8d694cc48bba0999b7dac658538a08159944a87 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 28 Feb 2025 13:05:44 +0100 Subject: [PATCH 089/239] format stuff --- src/public/css/createProfile.css | 50 +++---- src/public/css/invite.css | 2 +- src/public/css/seca.css | 3 - src/views/createProfile.ejs | 222 ++++++++++++++++--------------- 4 files changed, 141 insertions(+), 136 deletions(-) diff --git a/src/public/css/createProfile.css b/src/public/css/createProfile.css index f37d2cf..e9d02c1 100644 --- a/src/public/css/createProfile.css +++ b/src/public/css/createProfile.css @@ -1,31 +1,33 @@ +@media (max-width: 768px) { + .over-background > *:first-child { + margin-bottom: 5px; + } -@media (max-width: 768px) {.over-background > *:first-child { - margin-bottom: 5px; + .over-background > *:last-child { + margin-top: 5px; + } + + .over-background > * { + margin-bottom: 5px; + margin-top: 5px; + } } -.over-background > *:last-child { - margin-top: 5px; +@media (min-width: 769px) { + .over-background > *:first-child { + margin-bottom: 10px; + } + + .over-background > *:last-child { + margin-top: 10px; + } + + .over-background > * { + margin-bottom: 10px; + margin-top: 10px; + } } -.over-background > * { - margin-bottom: 5px; - margin-top: 5px; -}} - - -@media (min-width: 769px) {.over-background > *:first-child { - margin-bottom: 10px; -} - -.over-background > *:last-child { - margin-top: 10px; -} - -.over-background > * { - margin-bottom: 10px; - margin-top: 10px; -}} - .badges { display: grid; grid-template-columns: repeat(2, 1fr); @@ -59,4 +61,4 @@ h2 { margin-top: 0; margin-bottom: 5px; -} \ No newline at end of file +} diff --git a/src/public/css/invite.css b/src/public/css/invite.css index 6e3d2f3..bdb4779 100644 --- a/src/public/css/invite.css +++ b/src/public/css/invite.css @@ -4,7 +4,7 @@ } #invite-card-content { - width:100%; + width: 100%; } } diff --git a/src/public/css/seca.css b/src/public/css/seca.css index e23a1bd..6d89de4 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -105,7 +105,6 @@ h1 { height: 50px; } - .button-group button { border: 0; padding: 1em; @@ -158,8 +157,6 @@ h1 { margin-bottom: 1vh; } - - .text-warning { color: red; font-weight: 800; diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 74381f8..3613440 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -11,117 +11,123 @@
-
-
-

Crea tu perfil

-

Tu clave de Nostr ya es parte de la seca.

-

Añade detalles a tu perfil para poder empezar a comerciar.

-
-
-
-
- - -
-
-

Métodos de contacto

-
-
-

- Tus métodos de contacto son necesarios para hablar con otros - miembros y poder comerciar. -

-

- Tus métodos de contacto se guardan encriptados con tu clave de - nostr. laseca nunca puede verlos. Solo los miembros con los que - tú decidas podrán verlos. -

-

Debes añadir al menos un método de contacto para completar tu perfil.

-
- -
-
- -

Teléfono

- -
-
- -

Whatsapp

- -
-
- -

Telegram

- -
-
- -

Email

- -
-
- -

Nostr

- -
-
- -

Signal

- -
-
+
+
+

Crea tu perfil

+

Tu clave de Nostr ya es parte de la seca.

+

Añade detalles a tu perfil para poder empezar a comerciar.

+
+
+
+
+ + +
+
+

Métodos de contacto

+
+
+

+ Tus métodos de contacto son necesarios para hablar con otros + miembros y poder comerciar. +

+

+ Tus métodos de contacto se guardan encriptados con tu clave de + nostr. laseca nunca puede verlos. Solo los miembros con los + que tú decidas podrán verlos. +

+

+ Debes añadir al menos un método de contacto para completar tu + perfil. +

+
+ +
+
+ +

Teléfono

+ +
+
+ +

Whatsapp

+ +
+
+ +

Telegram

+ +
+
+ +

Email

+ +
+
+ +

Nostr

+ +
+
+ +

Signal

+ +
+
+
+
-
- -
-
- -

- ¡Bien! Tu perfil está completo. Te estamos llevando a la aplicación... -

-
+
+ +

+ ¡Bien! Tu perfil está completo. Te estamos llevando a la aplicación... +

+
From 1996299d8d8b0125514f56ef48ba9426a5920322 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 28 Feb 2025 13:21:49 +0100 Subject: [PATCH 090/239] pretty navbar --- src/public/css/seca.css | 18 ++++++++++- .../img/laseca-logo-transparent-textonly.svg | 30 +++++++++++++++++++ src/views/partials/appCommonHeader.ejs | 4 +-- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/public/img/laseca-logo-transparent-textonly.svg diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 6d89de4..02ef0e5 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -21,6 +21,12 @@ .top-notification-good { top: 25px; } + + + #app-nav-bar > #laseca-logo { + height: 30px; + } + } @media (min-width: 769px) { @@ -41,8 +47,18 @@ .top-notification-good { top: 50px; } + + #app-nav-bar > #laseca-logo { + height: 30px; + } } +#app-nav-bar > #laseca-logo { + margin-top: 0; + transform: translateY(20%); +} + + body { background-color: #bdd5ea; } @@ -140,7 +156,7 @@ h1 { .over-background { background-color: white; border-radius: 1vw; - padding: 2vw; + padding: 10px; margin: 1%; } diff --git a/src/public/img/laseca-logo-transparent-textonly.svg b/src/public/img/laseca-logo-transparent-textonly.svg new file mode 100644 index 0000000..9884298 --- /dev/null +++ b/src/public/img/laseca-logo-transparent-textonly.svg @@ -0,0 +1,30 @@ + + + +LASECA diff --git a/src/views/partials/appCommonHeader.ejs b/src/views/partials/appCommonHeader.ejs index c41181b..ae7f61b 100644 --- a/src/views/partials/appCommonHeader.ejs +++ b/src/views/partials/appCommonHeader.ejs @@ -1,5 +1,5 @@ -
-

la seca

+
+ From 0dd14eae8e3e60cf3112be960300898f0905b44d Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 28 Feb 2025 13:37:10 +0100 Subject: [PATCH 091/239] split card --- src/public/css/offers.css | 4 ++++ src/public/javascript/offers.js | 10 +++------- src/views/offers.ejs | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index b9b32eb..f257a49 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -67,6 +67,10 @@ } } +#create-offer-root { + display: none; +} + #create-offer-controls { text-align: center; } diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 054d7fa..22e88bc 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -2,8 +2,7 @@ const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); const closeOfferControls = document.getElementById('close-offer-controls-x'); -const createOfferControls = document.getElementById('create-offer-controls'); - +const createOfferRoot = document.getElementById('create-offer-root'); const buyOrSellButtonGroup = document.getElementById( 'button-group-buy-or-sell' ); @@ -46,11 +45,8 @@ const publishOfferButton = document.getElementById('button-submit-offer'); const offerCreatedPopup = document.getElementById('offer-created-confirmation'); function toggleCreateOfferControls() { - createOfferControls.style.display = - createOfferControls.style.display === 'block' ? 'none' : 'block'; - - buttonStartCreateOffer.style.display = - buttonStartCreateOffer.style.display === 'block' ? 'none' : 'block'; + createOfferRoot.style.display = + createOfferRoot.style.display === 'block' ? 'none' : 'block'; } function modifyPremiumValue(delta) { diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 223a408..748992c 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -19,8 +19,9 @@ > Crear nueva oferta - - +
+
Date: Fri, 28 Feb 2025 15:53:40 +0100 Subject: [PATCH 092/239] show offers panel --- src/public/css/offers.css | 4 ++++ src/public/javascript/offers.js | 11 +++++++++++ src/views/offers.ejs | 11 ++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index f257a49..2e0a0fb 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -71,6 +71,10 @@ display: none; } +#view-my-offers-root { + display: none; +} + #create-offer-controls { text-align: center; } diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 22e88bc..74b23a6 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -1,8 +1,10 @@ const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); +const buttonViewMyOffers = document.getElementById('button-view-my-offers'); const closeOfferControls = document.getElementById('close-offer-controls-x'); const createOfferRoot = document.getElementById('create-offer-root'); +const viewMyOffersRoot = document.getElementById('view-my-offers-root'); const buyOrSellButtonGroup = document.getElementById( 'button-group-buy-or-sell' ); @@ -49,6 +51,11 @@ function toggleCreateOfferControls() { createOfferRoot.style.display === 'block' ? 'none' : 'block'; } +function toggleViewMyOffersPanel() { + viewMyOffersRoot.style.display = + viewMyOffersRoot.style.display === 'block' ? 'none' : 'block'; +} + function modifyPremiumValue(delta) { const regexExpression = /-*\d+/; const numValue = parseInt(premiumValue.innerText.match(regexExpression)[0]); @@ -169,6 +176,10 @@ buttonStartCreateOffer.addEventListener('click', () => { toggleCreateOfferControls(); }); +buttonViewMyOffers.addEventListener('click', () => { + toggleViewMyOffersPanel(); +}); + closeOfferControls.addEventListener('click', () => { toggleCreateOfferControls(); }); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 748992c..69ee865 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -12,13 +12,19 @@ <%- include("partials/appCommonHeader") %>
+ +
@@ -179,6 +185,9 @@
+
+
+
Date: Fri, 28 Feb 2025 16:14:39 +0100 Subject: [PATCH 093/239] content --- src/public/css/offers.css | 10 ++++++++++ src/views/offers.ejs | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 2e0a0fb..2d85788 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -75,6 +75,16 @@ display: none; } +#view-my-offers-root { + display: none; + text-align: center; +} + +#view-my-offers-root > * { + margin-left:auto; + margin-right: auto; +} + #create-offer-controls { text-align: center; } diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 69ee865..06e5254 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -187,7 +187,12 @@
- +

Mis ofertas

+

Vaya, no hay nada por aquí...

+
+ Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu oferta la pueden ver tus confiados y los confidados de tus confiados. Se pueden usar billetes grandes. + +
Date: Fri, 28 Feb 2025 16:15:18 +0100 Subject: [PATCH 094/239] format --- src/public/css/offers.css | 4 ++-- src/public/css/seca.css | 3 --- src/views/offers.ejs | 8 +++++--- src/views/partials/appCommonHeader.ejs | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 2d85788..5468e9f 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -81,9 +81,9 @@ } #view-my-offers-root > * { - margin-left:auto; + margin-left: auto; margin-right: auto; -} +} #create-offer-controls { text-align: center; diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 02ef0e5..28962f0 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -22,11 +22,9 @@ top: 25px; } - #app-nav-bar > #laseca-logo { height: 30px; } - } @media (min-width: 769px) { @@ -58,7 +56,6 @@ transform: translateY(20%); } - body { background-color: #bdd5ea; } diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 06e5254..b638288 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -24,7 +24,6 @@ > Crear nueva oferta -
@@ -190,8 +189,11 @@

Mis ofertas

Vaya, no hay nada por aquí...

- Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu oferta la pueden ver tus confiados y los confidados de tus confiados. Se pueden usar billetes grandes. - + Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres + comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va + mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu + oferta la pueden ver tus confiados y los confidados de tus confiados. Se + pueden usar billetes grandes.
- + From 9e2af37158257b249d680bdaccbecf440a105e1a Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 28 Feb 2025 23:58:51 +0100 Subject: [PATCH 095/239] endpoint workzzz --- src/routes/apiRoutes.js | 27 ++++++++++++++++++++ src/services/offerService.js | 49 +++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 547cdb3..7ff2616 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -234,4 +234,31 @@ router.post( } ); +router.get( + '/publickey-offers', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + console.log('elo'); + const publicKey = req.cookies.publicKey; + + const offers = await offerService.getOffersByPublicKey(publicKey); + + if (!offers) { + return res.status(404).json({ + success: true, + message: 'No offers posted by this public key.', + }); + } + + if (offers) { + return res.status(200).json({ + success: true, + message: 'Offers found', + data: offers, + }); + } + } +); + module.exports = router; diff --git a/src/services/offerService.js b/src/services/offerService.js index 56ef780..4defc25 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -27,4 +27,51 @@ async function createOffer(publicKey, offerDetails) { created_at: new Date().toISOString(), }); } -module.exports = { createOffer }; + +async function getOffersByPublicKey(publicKey) { + const offers = await OfferCreated.findAll({ + where: { + public_key: publicKey, + }, + }); + + console.log(offers); + + if (!offers) { + return []; + } + + const offersToReturn = []; + if (offers) { + for (const someOffer of offers) { + const offerDetails = await OfferDetailsSet.findOne({ + where: { + offer_uuid: someOffer.uuid, + }, + order: [['created_at', 'DESC']], + }); + + console.log(offerDetails); + + offersToReturn.push({ + uuid: someOffer.uuid, + public_key: someOffer.public_key, + wants: offerDetails.wants, + premium: offerDetails.premium, + trade_amount_eur: offerDetails.trade_amount_eur, + location_details: offerDetails.location_details, + time_availability_details: offerDetails.time_availability_details, + show_offer_to_trusted: offerDetails.show_offer_to_trusted, + show_offer_to_trusted_trusted: + offerDetails.show_offer_to_trusted_trusted, + show_offer_to_all_members: offerDetails.show_offer_to_all_members, + is_onchain_accepted: offerDetails.is_onchain_accepted, + is_lightning_accepted: offerDetails.is_lightning_accepted, + are_big_notes_accepted: offerDetails.are_big_notes_accepted, + }); + } + } + + return offersToReturn; +} +module.exports = { createOffer, getOffersByPublicKey }; From 0e0a094dc87c0ca409f5afe29ebea2f06827460b Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 1 Mar 2025 23:18:18 +0100 Subject: [PATCH 096/239] fe can read from back --- src/app.js | 2 ++ src/public/javascript/offers.js | 44 +++++++++++++++++++++++++++++++++ src/services/offerService.js | 4 +-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/app.js b/src/app.js index 47430d0..adf0dbe 100644 --- a/src/app.js +++ b/src/app.js @@ -24,6 +24,8 @@ app.use('/api', apiRoutes); app.use(express.static(path.join(__dirname, 'public'))); +app.disable('etag'); //avoids 304 responses + app.listen(port, () => { console.log(`Server started on port ${port}`); }); diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 74b23a6..e3222c6 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -172,6 +172,50 @@ async function publishOffer() { toggleCreateOfferControls(); } +class Offer { + constructor(offerData) { + this.uuid = offerData.uuid; + this.public_key = offerData.public_key; + this.wants = offerData.wants; + this.premium = offerData.premium; + this.trade_amount_eur = offerData.trade_amount_eur; + this.location_details = offerData.location_details; + this.time_availability_details = offerData.time_availability_details; + this.show_offer_to_trusted = offerData.show_offer_to_trusted; + this.show_offer_to_trusted_trusted = + offerData.show_offer_to_trusted_trusted; + this.show_offer_to_all_members = offerData.show_offer_to_all_members; + this.is_onchain_accepted = offerData.is_onchain_accepted; + this.is_lightning_accepted = offerData.is_lightning_accepted; + this.are_big_notes_accepted = offerData.are_big_notes_accepted; + this.created_at = offerData.created_at; + this.last_updated_at = offerData.last_updated_at; + } +} + +class MyOffers { + constructor() { + this.offers = []; + } + + async getOffersFromApi() { + const offersResponse = await fetch('/api/publickey-offers'); + + if (!offersResponse.ok) { + this.offers = []; + } + + const offersData = (await offersResponse.json()).data; + if (offersResponse.ok) { + for (const record of offersData) { + this.offers.push(new Offer(record)); + } + } + } + + async render() {} +} + buttonStartCreateOffer.addEventListener('click', () => { toggleCreateOfferControls(); }); diff --git a/src/services/offerService.js b/src/services/offerService.js index 4defc25..b2d557c 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -51,8 +51,6 @@ async function getOffersByPublicKey(publicKey) { order: [['created_at', 'DESC']], }); - console.log(offerDetails); - offersToReturn.push({ uuid: someOffer.uuid, public_key: someOffer.public_key, @@ -68,6 +66,8 @@ async function getOffersByPublicKey(publicKey) { is_onchain_accepted: offerDetails.is_onchain_accepted, is_lightning_accepted: offerDetails.is_lightning_accepted, are_big_notes_accepted: offerDetails.are_big_notes_accepted, + created_at: someOffer.created_at, + last_updated_at: offerDetails.created_at, }); } } From dab201f0699d36baa0e61d392930c00f5a2d9957 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 3 Mar 2025 10:32:57 +0100 Subject: [PATCH 097/239] improving description --- src/public/css/offers.css | 6 +++ src/public/javascript/offers.js | 85 +++++++++++++++++++++++++++++++-- src/views/offers.ejs | 4 +- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 5468e9f..0aa1945 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -85,6 +85,12 @@ margin-right: auto; } +.myoffer-card { + text-align: start; + margin-top: 5px; + margin-bottom: 5px; +} + #create-offer-controls { text-align: center; } diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index e3222c6..4c5c552 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -46,6 +46,8 @@ const publishOfferButton = document.getElementById('button-submit-offer'); const offerCreatedPopup = document.getElementById('offer-created-confirmation'); +const ownOffersContainer = document.getElementById('own-offers-container'); + function toggleCreateOfferControls() { createOfferRoot.style.display = createOfferRoot.style.display === 'block' ? 'none' : 'block'; @@ -194,7 +196,8 @@ class Offer { } class MyOffers { - constructor() { + constructor(ownOffersContainerElement) { + this.ownOffersContainerElement = ownOffersContainerElement; this.offers = []; } @@ -213,14 +216,88 @@ class MyOffers { } } - async render() {} + async render() { + if (!this.offers) { + this.ownOffersContainerElement.innerHTML = + '

Vaya, no hay nada por aquí...

'; + return; + } + + let offersHTML = ''; + + for (const someOffer of this.offers) { + let tradeDescription; + if (someOffer.wants === 'BTC') { + tradeDescription = `Vendes ${someOffer.trade_amount_eur} €. Compras 100 000 sats. `; + } else { + tradeDescription = `Vendes 100 000 sats. Compras ${someOffer.trade_amount_eur} €. `; + } + tradeDescription += `El premium es de ${someOffer.premium}%.`; + + let paymentMethodsDescription = ''; + if (someOffer.is_onchain_accepted) { + paymentMethodsDescription += 'Se acepta Bitcoin onchain. '; + } + if (someOffer.is_lightning_accepted) { + paymentMethodsDescription += 'Se acepta Bitcoin Lightning.'; + } + + let visibilityDescription = 'La oferta es visible para: '; + if (someOffer.show_offer_to_trusted) { + visibilityDescription += 'Tus confiados'; + } + if (someOffer.show_offer_to_trusted_trusted) { + visibilityDescription += ', los confiados de tus confiados'; + } + if (someOffer.show_offer_to_all_members) { + visibilityDescription += ', todos los miembros'; + } + + offersHTML += ` +
+
+ ${tradeDescription} +
+
+ ${paymentMethodsDescription} +
+
+ ${visibilityDescription} +
+
+
+
    +
  • uuid: ${someOffer.uuid}
  • +
  • public_key: ${someOffer.public_key}
  • +
  • wants: ${someOffer.wants}
  • +
  • premium: ${someOffer.premium}
  • +
  • trade_amount_eur: ${someOffer.trade_amount_eur}
  • +
  • location_details: ${someOffer.location_details}
  • +
  • time_availability_details: ${someOffer.time_availability_details}
  • +
  • show_offer_to_trusted: ${someOffer.show_offer_to_trusted}
  • +
  • show_offer_to_trusted_trusted: ${someOffer.show_offer_to_trusted_trusted}
  • +
  • show_offer_to_all_members: ${someOffer.show_offer_to_all_members}
  • +
  • is_onchain_accepted: ${someOffer.is_onchain_accepted}
  • +
  • is_lightning_accepted: ${someOffer.is_lightning_accepted}
  • +
  • are_big_notes_accepted: ${someOffer.are_big_notes_accepted}
  • +
  • created_at: ${someOffer.created_at}
  • +
  • last_updated_at: ${someOffer.last_updated_at}
  • +
+
+ `; + } + + this.ownOffersContainerElement.innerHTML = offersHTML; + } } buttonStartCreateOffer.addEventListener('click', () => { toggleCreateOfferControls(); }); -buttonViewMyOffers.addEventListener('click', () => { +buttonViewMyOffers.addEventListener('click', async () => { + await myOffers.getOffersFromApi(); + await myOffers.render(); toggleViewMyOffersPanel(); }); @@ -263,3 +340,5 @@ publishOfferButton.addEventListener('click', () => { }); updateBtcInput(); + +const myOffers = new MyOffers(ownOffersContainer); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index b638288..4b16445 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -187,8 +187,8 @@

Mis ofertas

-

Vaya, no hay nada por aquí...

-
+
+

Vaya, no hay nada por aquí...

Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu From 0c6ad8bd8552e71c03a6658f31ae08831a47ab5e Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 3 Mar 2025 10:53:18 +0100 Subject: [PATCH 098/239] descriptions --- src/public/css/offers.css | 6 ++++++ src/public/css/seca.css | 6 ++++++ src/public/javascript/offers.js | 18 +++++++++++++++--- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 0aa1945..65fe5e2 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -91,6 +91,12 @@ margin-bottom: 5px; } +.myoffer-card > div { + max-width: 200px; + display: inline-block; + padding: 5px; +} + #create-offer-controls { text-align: center; } diff --git a/src/public/css/seca.css b/src/public/css/seca.css index 28962f0..07f3895 100644 --- a/src/public/css/seca.css +++ b/src/public/css/seca.css @@ -162,6 +162,12 @@ h1 { box-shadow: 0 0 13px #ccc; padding: 20px; } + +.subtle-box { + border-radius: 10px; + border: 1px solid rgb(202, 202, 202); +} + #login-card > * { text-align: center; margin-right: auto; diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 4c5c552..18ac641 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -253,17 +253,29 @@ class MyOffers { visibilityDescription += ', todos los miembros'; } + const whereAndWhenDescription = `Te va bien quedar: ${someOffer.location_details} | ${someOffer.time_availability_details}.`; + + let otherDescription = ''; + if (someOffer.are_big_notes_accepted) { + otherDescription += 'Se aceptan billetes grandes (100, 200, 500).'; + } offersHTML += `
-
+
${tradeDescription}
-
+
${paymentMethodsDescription}
-
+
${visibilityDescription}
+
+ ${whereAndWhenDescription} +
+
+ ${otherDescription} +
-
-
-
- -
-

Añade los detalles de tu oferta

-
-
+
+
+
+

Añade los detalles de tu oferta

+
+
+ +
+
+
+

Premium

+
+
+
0%
+
+ +
+
+
+

+ Tu precio: 90 000€/BTC +

+

+ (Precio mercado: 83 000€/BTC) +

+
+
+
+
+

¿Cuánto?

+
+
+ +
+ +
+
+
+ +
+ SAT +
+
+
+
+
+

¿Dónde y cuándo?

+
+ + +
+
+
+

¿Cómo se mueve el Bitcoin?

+
+ +
+
+ +
+
+
+

¿Quién puede ver la oferta?

+
+ +
+
+ +
+
+ +
+
+
+

Extras

+
+ +
+
+
-
-
-

Premium

-
-
-
0%
-
- -
-
-
-

- Tu precio: 90 000€/BTC -

-

- (Precio mercado: 83 000€/BTC) -

-
+
+
-
-

¿Cuánto?

-
-
- -
- -
-
-
- -
- SAT -
-
-
-
-
-

¿Dónde y cuándo?

-
- - -
-
-
-

¿Cómo se mueve el Bitcoin?

-
- -
-
- -
-
-
-

¿Quién puede ver la oferta?

-
- -
-
- -
-
- -
-
-
-

Extras

-
- -
-
-
- -
From aea850c642b879bfd78a4484fcb3d5f79876ab98 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 4 Mar 2025 17:55:30 +0100 Subject: [PATCH 109/239] move button --- src/public/css/offers.css | 4 ++-- src/public/javascript/offers.js | 6 ++++-- src/views/offers.ejs | 33 +++++++++++++++------------------ 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index 7c8590d..e89f523 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -387,5 +387,5 @@ #close-offer { margin-left: auto; - margin-right:auto; -} \ No newline at end of file + margin-right: auto; +} diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 3f352ac..cfb55f6 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -617,8 +617,10 @@ for (const btcMethodCheckbox of btcMethodCheckboxes) { }); } -publishOfferButton.addEventListener('click', () => { - publishOffer(); +publishOfferButton.addEventListener('click', async () => { + await publishOffer(); + await myOffers.getOffersFromApi(); + await myOffers.render(); }); updateBtcInput(); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index e96425f..5822afb 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -18,12 +18,18 @@ > Ver mis ofertas - +
+
+

Mis ofertas

+ +
+

Vaya, no hay nada por aquí...

+ Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres + comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va + mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu + oferta la pueden ver tus confiados y los confidados de tus confiados. Se + pueden usar billetes grandes. +
@@ -181,22 +187,13 @@
- +
-
-

Mis ofertas

-
-

Vaya, no hay nada por aquí...

- Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres - comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va - mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu - oferta la pueden ver tus confiados y los confidados de tus confiados. Se - pueden usar billetes grandes. -
-
Date: Wed, 5 Mar 2025 00:26:30 +0100 Subject: [PATCH 110/239] checkboxes now work properly --- src/public/javascript/offers.js | 24 ++++++++++++++++++++++++ src/views/offers.ejs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index cfb55f6..f3fd9b6 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -119,6 +119,22 @@ function validateBitcoinMethodCheckboxes(clickedCheckbox) { } } +function applyTrustCheckboxConstraints(pressedCheckbox) { + if (pressedCheckbox === myTrustedTrustedCheckbox) { + console.log('first case!'); + if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { + allMembersCheckbox.checked = false; + } + } + + if (pressedCheckbox === allMembersCheckbox) { + console.log('second case!'); + if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { + myTrustedTrustedCheckbox.checked = true; + } + } +} + async function publishOffer() { let wants; if (buyButton.classList.contains('selected')) { @@ -617,6 +633,14 @@ for (const btcMethodCheckbox of btcMethodCheckboxes) { }); } +myTrustedTrustedCheckbox.addEventListener('click', () => { + applyTrustCheckboxConstraints(myTrustedTrustedCheckbox); +}); + +allMembersCheckbox.addEventListener('click', () => { + applyTrustCheckboxConstraints(allMembersCheckbox); +}); + publishOfferButton.addEventListener('click', async () => { await publishOffer(); await myOffers.getOffersFromApi(); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 5822afb..ea86e5a 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -21,7 +21,7 @@

Mis ofertas

- +

Vaya, no hay nada por aquí...

Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres From 6930e0708c3e12b9822ba2035023c5cc3df3ab8b Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 00:28:56 +0100 Subject: [PATCH 111/239] remove old stuff, format --- src/views/offers.ejs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/views/offers.ejs b/src/views/offers.ejs index ea86e5a..17c03b7 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -21,14 +21,14 @@

Mis ofertas

- +

Vaya, no hay nada por aquí...

- Quieres comprar BTC a un premium de 3% (precio: 93.000€/BTC). Quieres - comprar 100€/102 100SAT. Puedes quedar en: "Cualquier parte", y te va - mejor: "en cualquier momento". Se puede usar Onchain y Lightning. Tu - oferta la pueden ver tus confiados y los confidados de tus confiados. Se - pueden usar billetes grandes.
From 9f807a783dbf483293213ff5553095b98456fb0f Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 00:40:34 +0100 Subject: [PATCH 112/239] new model --- src/models/OfferDeleted.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/models/OfferDeleted.js diff --git a/src/models/OfferDeleted.js b/src/models/OfferDeleted.js new file mode 100644 index 0000000..036a31a --- /dev/null +++ b/src/models/OfferDeleted.js @@ -0,0 +1,27 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../database'); + +const OfferDeleted = sequelize.define( + 'OfferCreated', + { + uuid: { + type: DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + offer_uuid: { + type: DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'offer_deleted', + } +); + +module.exports = OfferDeleted; From a5e519f63415c764eecbbe0a20fa37f2c17a3988 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 00:48:33 +0100 Subject: [PATCH 113/239] service entry --- src/services/offerService.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/services/offerService.js b/src/services/offerService.js index b2d557c..0b746d5 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -2,6 +2,9 @@ const uuid = require('uuid'); const OfferCreated = require('../models/OfferCreated'); const OfferDetailsSet = require('../models/OfferDetailsSet'); +const OfferDeleted = require('../models/OfferDeleted'); + +const errors = require('../errors'); async function createOffer(publicKey, offerDetails) { const offerCreated = await OfferCreated.create({ @@ -28,6 +31,25 @@ async function createOffer(publicKey, offerDetails) { }); } +async function deleteOffer(offerUuid) { + const offerExists = Boolean( + await OfferCreated.findOne({ where: { uuid: offerUuid } }) + ); + const offerHasBeenDeleted = Boolean( + await OfferDeleted.findOne({ where: { offer_uuid: offerUuid } }) + ); + + if (!offerExists || offerHasBeenDeleted) { + throw new errors.NotFoundError(`Could not find the offer.`); + } + + return OfferDeleted.create({ + uuid: uuid.v7(), + offer_uuid: offerUuid, + created_at: new Date().toISOString(), + }); +} + async function getOffersByPublicKey(publicKey) { const offers = await OfferCreated.findAll({ where: { From 5134982870f414af6b234b1b6eeea61fd6b36118 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 11:10:06 +0100 Subject: [PATCH 114/239] fix bug --- src/models/OfferDeleted.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/OfferDeleted.js b/src/models/OfferDeleted.js index 036a31a..aa1b71a 100644 --- a/src/models/OfferDeleted.js +++ b/src/models/OfferDeleted.js @@ -2,7 +2,7 @@ const { DataTypes } = require('sequelize'); const sequelize = require('../database'); const OfferDeleted = sequelize.define( - 'OfferCreated', + 'OfferDeleted', { uuid: { type: DataTypes.UUID, From ab41f50142610835533ec42010053427ea62a113 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 11:13:01 +0100 Subject: [PATCH 115/239] export service function --- src/services/offerService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/offerService.js b/src/services/offerService.js index 0b746d5..fe6273d 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -96,4 +96,4 @@ async function getOffersByPublicKey(publicKey) { return offersToReturn; } -module.exports = { createOffer, getOffersByPublicKey }; +module.exports = { createOffer, getOffersByPublicKey, deleteOffer }; From 2b48c7f5e23aa7f81239f613a777b149e48118a9 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 11:13:10 +0100 Subject: [PATCH 116/239] endpont --- src/routes/apiRoutes.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 7ff2616..02bcc56 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -234,6 +234,35 @@ router.post( } ); +router.delete( + '/offer/:offerUuid', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const offerUuid = req.params.offerUuid; + + try { + await offerService.deleteOffer(offerUuid); + } catch (error) { + if (error instanceof errors.NotFoundError) { + return res.status(404).json({ + success: false, + message: 'Offer not found for the given public key.', + }); + } + return res.status(500).json({ + success: false, + message: 'Unexpected error.', + }); + } + + return res.status(204).json({ + success: true, + message: 'Offer deleted successfully', + }); + } +); + router.get( '/publickey-offers', rejectIfNotAuthorizedMiddleware, From 26f549c9285433ae1b3aa7fda4a2952af73943e5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 11:29:20 +0100 Subject: [PATCH 117/239] button deletes --- src/public/css/offers.css | 1 + src/public/javascript/offers.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/public/css/offers.css b/src/public/css/offers.css index e89f523..17ac4b5 100644 --- a/src/public/css/offers.css +++ b/src/public/css/offers.css @@ -214,6 +214,7 @@ .offer-action-area { cursor: pointer; margin-right: 20px; + padding: 3px; } .offer-long-text { diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index f3fd9b6..5bcd2cc 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -525,6 +525,7 @@ class Offer { const editActionArea = document.createElement('div'); editActionArea.classList.add('offer-action-area'); + editActionArea.classList.add('subtle-box'); const editActionIcon = document.createElement('img'); editActionIcon.src = '/img/edit.svg'; const editActionText = document.createElement('p'); @@ -533,11 +534,15 @@ class Offer { const deleteActionArea = document.createElement('div'); deleteActionArea.classList.add('offer-action-area'); + deleteActionArea.classList.add('subtle-box'); const deleteActionIcon = document.createElement('img'); deleteActionIcon.src = '/img/trash-can-darkred.svg'; const deleteActionText = document.createElement('p'); deleteActionText.innerText = 'Eliminar'; deleteActionArea.append(deleteActionIcon, deleteActionText); + deleteActionArea.addEventListener('click', async () => { + deleteOfferByUuid(this.uuid); + }); actionButtonsArea.append(editActionArea, deleteActionArea); @@ -589,6 +594,18 @@ class MyOffers { } } +async function deleteOfferByUuid(offerUuid) { + await fetch(`/api/offer/${offerUuid}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + + myOffers.getOffersFromApi(); + myOffers.render(); +} + buttonStartCreateOffer.addEventListener('click', () => { toggleCreateOfferModal(); }); From 76e6bee4116eac4a62800583ba9406aa603047e9 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 14:08:12 +0100 Subject: [PATCH 118/239] put factor creation in function --- src/app.js | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/app.js b/src/app.js index adf0dbe..9691247 100644 --- a/src/app.js +++ b/src/app.js @@ -1,31 +1,38 @@ -const express = require('express'); -const cookieParser = require('cookie-parser'); -const path = require('path'); +function createApp() { + const express = require('express'); + const cookieParser = require('cookie-parser'); + const path = require('path'); -const app = express(); -const port = 3000; + const app = express(); + const port = 3000; -app.use(cookieParser()); + app.set('port', port); -app.use(express.json()); + app.use(cookieParser()); -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); + app.use(express.json()); -const createSessionMiddleware = require('./middlewares/sessionMiddleware'); + app.set('view engine', 'ejs'); + app.set('views', path.join(__dirname, 'views')); -app.use(createSessionMiddleware); + const createSessionMiddleware = require('./middlewares/sessionMiddleware'); -const webRoutes = require('./routes/webRoutes'); -const apiRoutes = require('./routes/apiRoutes'); + app.use(createSessionMiddleware); -app.use('/', webRoutes); -app.use('/api', apiRoutes); + const webRoutes = require('./routes/webRoutes'); + const apiRoutes = require('./routes/apiRoutes'); -app.use(express.static(path.join(__dirname, 'public'))); + app.use('/', webRoutes); + app.use('/api', apiRoutes); -app.disable('etag'); //avoids 304 responses + app.use(express.static(path.join(__dirname, 'public'))); -app.listen(port, () => { - console.log(`Server started on port ${port}`); + app.disable('etag'); //avoids 304 responses + + return app; +} + +const app = createApp(); +app.listen(app.get('port'), () => { + console.log(`Server started on port ${app.get('port')}`); }); From ba9fa8440742b259a08f15325e4ae29f57b6ea8d Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:04:27 +0100 Subject: [PATCH 119/239] provider-ize profile service --- src/app.js | 18 ++++-- ...directIfMissingProfileDetailsMiddleware.js | 5 +- src/routes/apiRoutes.js | 6 +- src/services/profileService.js | 61 +++++++++---------- 4 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/app.js b/src/app.js index 9691247..49abb0f 100644 --- a/src/app.js +++ b/src/app.js @@ -1,8 +1,15 @@ -function createApp() { - const express = require('express'); - const cookieParser = require('cookie-parser'); - const path = require('path'); +const express = require('express'); +const cookieParser = require('cookie-parser'); +const path = require('path'); +function buildDependencies() { + const dependencies = {}; + + dependencies.sequelize = require('./database'); + return dependencies; +} + +function createApp(dependencies) { const app = express(); const port = 3000; @@ -32,7 +39,8 @@ function createApp() { return app; } -const app = createApp(); +const dependencies = buildDependencies(); +const app = createApp(dependencies); app.listen(app.get('port'), () => { console.log(`Server started on port ${app.get('port')}`); }); diff --git a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js index 934048d..090bc31 100644 --- a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js +++ b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js @@ -1,4 +1,7 @@ -const profileService = require('../services/profileService'); +const profileServiceProvider = require('../services/profileService'); +const ContactDetailsSet = require('../models/ContactDetailsSet'); +const NymSet = require('../models/NymSet'); +const profileService = profileServiceProvider(ContactDetailsSet, NymSet); async function redirectIfMissingProfileDetailsMiddleware(req, res, next) { const publicKey = req.cookies.publicKey; diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 02bcc56..9d5516c 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -4,12 +4,16 @@ const invitesService = require('../services/invitesService'); const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); const sessionService = require('../services/sessionService'); -const profileService = require('../services/profileService'); +const profileServiceProvider = require('../services/profileService'); const offerService = require('../services/offerService'); const errors = require('../errors'); const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); const rejectIfNotAuthorizedMiddleware = require('../middlewares/rejectIfNotAuthorizedMiddleware'); +const ContactDetailsSet = require('../models/ContactDetailsSet'); +const NymSet = require('../models/NymSet'); +const profileService = profileServiceProvider(ContactDetailsSet, NymSet); + const router = express.Router(); router.get('/signup/nostr-challenge', async (req, res) => { diff --git a/src/services/profileService.js b/src/services/profileService.js index 5db0c24..2ea84c8 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -1,38 +1,35 @@ const uuid = require('uuid'); -const ContactDetailsSet = require('../models/ContactDetailsSet'); -const NymSet = require('../models/NymSet'); -async function setContactDetails(publicKey, encryptedContactDetails) { - return await ContactDetailsSet.create({ - uuid: uuid.v7(), - public_key: publicKey, - encrypted_contact_details: encryptedContactDetails, - created_at: new Date().toISOString(), - }); -} - -async function setNym(publicKey, nym) { - return await NymSet.create({ - uuid: uuid.v7(), - public_key: publicKey, - nym: nym, - created_at: new Date().toISOString(), - }); -} - -async function areProfileDetailsComplete(publicKey) { - const isNymSet = await NymSet.findOne({ where: { public_key: publicKey } }); - const areContactDetailsSet = await ContactDetailsSet.findOne({ - where: { +const profileServicesProvider = (ContactDetailsSet, NymSet) => { + async function setContactDetails(publicKey, encryptedContactDetails) { + return await ContactDetailsSet.create({ + uuid: uuid.v7(), public_key: publicKey, - }, - }); + encrypted_contact_details: encryptedContactDetails, + created_at: new Date().toISOString(), + }); + } - return isNymSet && areContactDetailsSet; -} + async function setNym(publicKey, nym) { + return await NymSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + nym: nym, + created_at: new Date().toISOString(), + }); + } -module.exports = { - setContactDetails, - setNym, - areProfileDetailsComplete, + async function areProfileDetailsComplete(publicKey) { + const isNymSet = await NymSet.findOne({ where: { public_key: publicKey } }); + const areContactDetailsSet = await ContactDetailsSet.findOne({ + where: { + public_key: publicKey, + }, + }); + + return isNymSet && areContactDetailsSet; + } + + return { setContactDetails, setNym, areProfileDetailsComplete }; }; +module.exports = profileServicesProvider; From 7db9e7f78c5c085a31e5eb44318b834e5c3d7e12 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:21:57 +0100 Subject: [PATCH 120/239] going somewhere, kinda --- src/app.js | 5 +- src/routes/webRoutes.js | 139 ++++++++++++++++++++++------------------ 2 files changed, 79 insertions(+), 65 deletions(-) diff --git a/src/app.js b/src/app.js index 49abb0f..9c89db5 100644 --- a/src/app.js +++ b/src/app.js @@ -5,7 +5,8 @@ const path = require('path'); function buildDependencies() { const dependencies = {}; - dependencies.sequelize = require('./database'); + const webRoutesProvider = new (require('./routes/webRoutes'))(); + dependencies.webRoutes = webRoutesProvider.provide(); return dependencies; } @@ -26,7 +27,7 @@ function createApp(dependencies) { app.use(createSessionMiddleware); - const webRoutes = require('./routes/webRoutes'); + const webRoutes = dependencies.webRoutes; const apiRoutes = require('./routes/apiRoutes'); app.use('/', webRoutes); diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index fd6585e..49ad7cd 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -7,68 +7,81 @@ const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddlew const redirectIfMissingProfileDetailsMiddleware = require('../middlewares/redirectIfMissingProfileDetailsMiddleware'); const redirectHomeIfAuthorized = require('../middlewares/redirectHomeIfAuthorized'); -router.get( - '/', - redirectHomeIfAuthorized, - redirectIfNotAuthorizedMiddleware, - (req, res) => { - res.redirect('/login'); +class WebRoutesProvider { + provide() { + router.get( + '/', + redirectHomeIfAuthorized, + redirectIfNotAuthorizedMiddleware, + (req, res) => { + res.redirect('/login'); + } + ); + + router.get('/login', redirectHomeIfAuthorized, (req, res) => { + res.render('login', { uuid: req.cookies.sessionUuid }); + }); + + router.get('/invite/:inviteUuid', async (req, res) => { + const { inviteUuid } = req.params; + + res.cookie('inviteUuid', inviteUuid, { + httpOnly: true, + maxAge: 86400000, + }); + + let invite; + try { + invite = await invitesService.getAppInvite(inviteUuid); + if (!invite) { + return res + .status(404) + .render('error', { message: 'Invite not found.' }); + } + + if (await invitesService.isAppInviteSpent(inviteUuid)) { + return res.status(410).render('invite_spent', { invite }); + } + } catch (error) { + console.error('Error fetching invite:', error); + return res + .status(500) + .render('error', { message: 'An error occurred' }); + } + + return res.render('invite', { invite }); + }); + + router.get( + '/createProfile', + redirectIfNotAuthorizedMiddleware, + async (req, res) => { + return res.status(200).render('createProfile'); + } + ); + + router.get( + '/home', + redirectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, + (req, res) => { + res.render('home', {}); + } + ); + + router.get( + '/offers', + redirectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, + (req, res) => { + res.render('offers', {}); + } + ); + + return router; } -); +} -router.get('/login', redirectHomeIfAuthorized, (req, res) => { - res.render('login', { uuid: req.cookies.sessionUuid }); -}); - -router.get('/invite/:inviteUuid', async (req, res) => { - const { inviteUuid } = req.params; - - res.cookie('inviteUuid', inviteUuid, { httpOnly: true, maxAge: 86400000 }); - - let invite; - try { - invite = await invitesService.getAppInvite(inviteUuid); - if (!invite) { - return res.status(404).render('error', { message: 'Invite not found.' }); - } - - if (await invitesService.isAppInviteSpent(inviteUuid)) { - return res.status(410).render('invite_spent', { invite }); - } - } catch (error) { - console.error('Error fetching invite:', error); - return res.status(500).render('error', { message: 'An error occurred' }); - } - - return res.render('invite', { invite }); -}); - -router.get( - '/createProfile', - redirectIfNotAuthorizedMiddleware, - async (req, res) => { - return res.status(200).render('createProfile'); - } -); - -router.get( - '/home', - redirectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - redirectIfMissingProfileDetailsMiddleware, - (req, res) => { - res.render('home', {}); - } -); - -router.get( - '/offers', - redirectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - redirectIfMissingProfileDetailsMiddleware, - (req, res) => { - res.render('offers', {}); - } -); - -module.exports = router; +module.exports = WebRoutesProvider; From efa855c60ac95b04ea377eb22fdf6ce9fc4dc570 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:39:31 +0100 Subject: [PATCH 121/239] split --- src/app.js | 9 +-------- src/dependencies.js | 12 ++++++++++++ src/routes/webRoutes.js | 22 ++++++++++++---------- 3 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 src/dependencies.js diff --git a/src/app.js b/src/app.js index 9c89db5..7ec68ee 100644 --- a/src/app.js +++ b/src/app.js @@ -1,14 +1,7 @@ const express = require('express'); const cookieParser = require('cookie-parser'); const path = require('path'); - -function buildDependencies() { - const dependencies = {}; - - const webRoutesProvider = new (require('./routes/webRoutes'))(); - dependencies.webRoutes = webRoutesProvider.provide(); - return dependencies; -} +const { buildDependencies } = require('./dependencies'); function createApp(dependencies) { const app = express(); diff --git a/src/dependencies.js b/src/dependencies.js new file mode 100644 index 0000000..bef3e82 --- /dev/null +++ b/src/dependencies.js @@ -0,0 +1,12 @@ +const express = require('express'); + +function buildDependencies() { + const dependencies = {}; + + const WebRoutesProvider = require('./routes/webRoutes'); + const webRoutesProvider = new WebRoutesProvider(express); + dependencies.webRoutes = webRoutesProvider.provide(); + return dependencies; +} + +module.exports = { buildDependencies }; diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 49ad7cd..8a9784f 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -1,6 +1,3 @@ -const express = require('express'); -const router = express.Router(); - const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotAuthorizedMiddleware'); const invitesService = require('../services/invitesService'); const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); @@ -8,8 +5,13 @@ const redirectIfMissingProfileDetailsMiddleware = require('../middlewares/redire const redirectHomeIfAuthorized = require('../middlewares/redirectHomeIfAuthorized'); class WebRoutesProvider { + constructor(express) { + this.express; + this.router = express.Router(); + } + provide() { - router.get( + this.router.get( '/', redirectHomeIfAuthorized, redirectIfNotAuthorizedMiddleware, @@ -18,11 +20,11 @@ class WebRoutesProvider { } ); - router.get('/login', redirectHomeIfAuthorized, (req, res) => { + this.router.get('/login', redirectHomeIfAuthorized, (req, res) => { res.render('login', { uuid: req.cookies.sessionUuid }); }); - router.get('/invite/:inviteUuid', async (req, res) => { + this.router.get('/invite/:inviteUuid', async (req, res) => { const { inviteUuid } = req.params; res.cookie('inviteUuid', inviteUuid, { @@ -52,7 +54,7 @@ class WebRoutesProvider { return res.render('invite', { invite }); }); - router.get( + this.router.get( '/createProfile', redirectIfNotAuthorizedMiddleware, async (req, res) => { @@ -60,7 +62,7 @@ class WebRoutesProvider { } ); - router.get( + this.router.get( '/home', redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, @@ -70,7 +72,7 @@ class WebRoutesProvider { } ); - router.get( + this.router.get( '/offers', redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, @@ -80,7 +82,7 @@ class WebRoutesProvider { } ); - return router; + return this.router; } } From 76efca914a5ce7cd68fc7fca96106a3242794e31 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:46:51 +0100 Subject: [PATCH 122/239] refactor middlewares --- src/dependencies.js | 14 +++++++++++++- src/routes/webRoutes.js | 36 ++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index bef3e82..1eeaac2 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -3,8 +3,20 @@ const express = require('express'); function buildDependencies() { const dependencies = {}; + const redirectIfNotAuthorizedMiddleware = require('./middlewares/redirectIfNotAuthorizedMiddleware'); + const attachPublicKeyMiddleware = require('./middlewares/attachPublicKeyMiddleware'); + const redirectIfMissingProfileDetailsMiddleware = require('./middlewares/redirectIfMissingProfileDetailsMiddleware'); + const redirectHomeIfAuthorized = require('./middlewares/redirectHomeIfAuthorized'); + + const middlewares = { + redirectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, + redirectHomeIfAuthorized, + }; + const WebRoutesProvider = require('./routes/webRoutes'); - const webRoutesProvider = new WebRoutesProvider(express); + const webRoutesProvider = new WebRoutesProvider({ express, middlewares }); dependencies.webRoutes = webRoutesProvider.provide(); return dependencies; } diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 8a9784f..4849c91 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -1,28 +1,28 @@ -const redirectIfNotAuthorizedMiddleware = require('../middlewares/redirectIfNotAuthorizedMiddleware'); const invitesService = require('../services/invitesService'); -const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); -const redirectIfMissingProfileDetailsMiddleware = require('../middlewares/redirectIfMissingProfileDetailsMiddleware'); -const redirectHomeIfAuthorized = require('../middlewares/redirectHomeIfAuthorized'); class WebRoutesProvider { - constructor(express) { - this.express; + constructor({ express, middlewares }) { this.router = express.Router(); + this.middlewares = middlewares; } provide() { this.router.get( '/', - redirectHomeIfAuthorized, - redirectIfNotAuthorizedMiddleware, + this.middlewares.redirectHomeIfAuthorized, + this.middlewares.redirectIfNotAuthorizedMiddleware, (req, res) => { res.redirect('/login'); } ); - this.router.get('/login', redirectHomeIfAuthorized, (req, res) => { - res.render('login', { uuid: req.cookies.sessionUuid }); - }); + this.router.get( + '/login', + this.middlewares.redirectHomeIfAuthorized, + (req, res) => { + res.render('login', { uuid: req.cookies.sessionUuid }); + } + ); this.router.get('/invite/:inviteUuid', async (req, res) => { const { inviteUuid } = req.params; @@ -56,7 +56,7 @@ class WebRoutesProvider { this.router.get( '/createProfile', - redirectIfNotAuthorizedMiddleware, + this.middlewares.redirectIfNotAuthorizedMiddleware, async (req, res) => { return res.status(200).render('createProfile'); } @@ -64,9 +64,9 @@ class WebRoutesProvider { this.router.get( '/home', - redirectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - redirectIfMissingProfileDetailsMiddleware, + this.middlewares.redirectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, + this.middlewares.redirectIfMissingProfileDetailsMiddleware, (req, res) => { res.render('home', {}); } @@ -74,9 +74,9 @@ class WebRoutesProvider { this.router.get( '/offers', - redirectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - redirectIfMissingProfileDetailsMiddleware, + this.middlewares.redirectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, + this.middlewares.redirectIfMissingProfileDetailsMiddleware, (req, res) => { res.render('offers', {}); } From b36f44ae35d63b0e91330fba4dad75bff1831b4d Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:50:51 +0100 Subject: [PATCH 123/239] refactor middlwares --- src/dependencies.js | 12 +----------- src/middlewares/index.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 src/middlewares/index.js diff --git a/src/dependencies.js b/src/dependencies.js index 1eeaac2..a28c54c 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -3,17 +3,7 @@ const express = require('express'); function buildDependencies() { const dependencies = {}; - const redirectIfNotAuthorizedMiddleware = require('./middlewares/redirectIfNotAuthorizedMiddleware'); - const attachPublicKeyMiddleware = require('./middlewares/attachPublicKeyMiddleware'); - const redirectIfMissingProfileDetailsMiddleware = require('./middlewares/redirectIfMissingProfileDetailsMiddleware'); - const redirectHomeIfAuthorized = require('./middlewares/redirectHomeIfAuthorized'); - - const middlewares = { - redirectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - redirectIfMissingProfileDetailsMiddleware, - redirectHomeIfAuthorized, - }; + const middlewares = require('./middlewares'); const WebRoutesProvider = require('./routes/webRoutes'); const webRoutesProvider = new WebRoutesProvider({ express, middlewares }); diff --git a/src/middlewares/index.js b/src/middlewares/index.js new file mode 100644 index 0000000..a1506b3 --- /dev/null +++ b/src/middlewares/index.js @@ -0,0 +1,15 @@ +const redirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); +const attachPublicKeyMiddleware = require('./attachPublicKeyMiddleware'); +const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); +const redirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); +const rejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); +const sessionMiddleware = require('./sessionMiddleware'); + +module.exports = { + redirectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, + redirectHomeIfAuthorized, + rejectIfNotAuthorizedMiddleware, + sessionMiddleware, +}; From 7cb914428175fb8a8fb9db1bef7bc4d1d0d22b82 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:53:13 +0100 Subject: [PATCH 124/239] more refactor --- src/app.js | 4 +--- src/dependencies.js | 1 + .../{sessionMiddleware.js => createSessionMiddleware.js} | 0 src/middlewares/index.js | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) rename src/middlewares/{sessionMiddleware.js => createSessionMiddleware.js} (100%) diff --git a/src/app.js b/src/app.js index 7ec68ee..0745caa 100644 --- a/src/app.js +++ b/src/app.js @@ -16,9 +16,7 @@ function createApp(dependencies) { app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); - const createSessionMiddleware = require('./middlewares/sessionMiddleware'); - - app.use(createSessionMiddleware); + app.use(dependencies.middlewares.createSessionMiddleware); const webRoutes = dependencies.webRoutes; const apiRoutes = require('./routes/apiRoutes'); diff --git a/src/dependencies.js b/src/dependencies.js index a28c54c..8bf8346 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -4,6 +4,7 @@ function buildDependencies() { const dependencies = {}; const middlewares = require('./middlewares'); + dependencies.middlewares = middlewares; const WebRoutesProvider = require('./routes/webRoutes'); const webRoutesProvider = new WebRoutesProvider({ express, middlewares }); diff --git a/src/middlewares/sessionMiddleware.js b/src/middlewares/createSessionMiddleware.js similarity index 100% rename from src/middlewares/sessionMiddleware.js rename to src/middlewares/createSessionMiddleware.js diff --git a/src/middlewares/index.js b/src/middlewares/index.js index a1506b3..6a56fa8 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -3,7 +3,7 @@ const attachPublicKeyMiddleware = require('./attachPublicKeyMiddleware'); const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); const redirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); const rejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); -const sessionMiddleware = require('./sessionMiddleware'); +const createSessionMiddleware = require('./createSessionMiddleware'); module.exports = { redirectIfNotAuthorizedMiddleware, @@ -11,5 +11,5 @@ module.exports = { redirectIfMissingProfileDetailsMiddleware, redirectHomeIfAuthorized, rejectIfNotAuthorizedMiddleware, - sessionMiddleware, + createSessionMiddleware, }; From db97f9fd80dd502a313d10d29cf026571be2db2d Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 15:55:13 +0100 Subject: [PATCH 125/239] web routes complete di-ized --- src/dependencies.js | 8 +++++++- src/routes/webRoutes.js | 9 ++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index 8bf8346..13a1af7 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -6,8 +6,14 @@ function buildDependencies() { const middlewares = require('./middlewares'); dependencies.middlewares = middlewares; + const invitesService = require('./services/invitesService'); + const WebRoutesProvider = require('./routes/webRoutes'); - const webRoutesProvider = new WebRoutesProvider({ express, middlewares }); + const webRoutesProvider = new WebRoutesProvider({ + express, + middlewares, + invitesService, + }); dependencies.webRoutes = webRoutesProvider.provide(); return dependencies; } diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js index 4849c91..b215aa5 100644 --- a/src/routes/webRoutes.js +++ b/src/routes/webRoutes.js @@ -1,9 +1,8 @@ -const invitesService = require('../services/invitesService'); - class WebRoutesProvider { - constructor({ express, middlewares }) { + constructor({ express, middlewares, invitesService }) { this.router = express.Router(); this.middlewares = middlewares; + this.invitesService = invitesService; } provide() { @@ -34,14 +33,14 @@ class WebRoutesProvider { let invite; try { - invite = await invitesService.getAppInvite(inviteUuid); + invite = await this.invitesService.getAppInvite(inviteUuid); if (!invite) { return res .status(404) .render('error', { message: 'Invite not found.' }); } - if (await invitesService.isAppInviteSpent(inviteUuid)) { + if (await this.invitesService.isAppInviteSpent(inviteUuid)) { return res.status(410).render('invite_spent', { invite }); } } catch (error) { From c923493108d7dbb7e2cb3703f0c50814803eb634 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:04:44 +0100 Subject: [PATCH 126/239] start api routes --- src/app.js | 7 +- src/dependencies.js | 4 + src/routes/apiRoutes.js | 552 +++++++++++++++++++++------------------- 3 files changed, 290 insertions(+), 273 deletions(-) diff --git a/src/app.js b/src/app.js index 0745caa..dc3cf83 100644 --- a/src/app.js +++ b/src/app.js @@ -18,11 +18,8 @@ function createApp(dependencies) { app.use(dependencies.middlewares.createSessionMiddleware); - const webRoutes = dependencies.webRoutes; - const apiRoutes = require('./routes/apiRoutes'); - - app.use('/', webRoutes); - app.use('/api', apiRoutes); + app.use('/', dependencies.webRoutes); + app.use('/api', dependencies.apiRoutes); app.use(express.static(path.join(__dirname, 'public'))); diff --git a/src/dependencies.js b/src/dependencies.js index 13a1af7..4e44210 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -15,6 +15,10 @@ function buildDependencies() { invitesService, }); dependencies.webRoutes = webRoutesProvider.provide(); + + const ApiRoutesProvider = require('./routes/apiRoutes'); + const apiRoutesProvider = new ApiRoutesProvider(); + dependencies.apiRoutes = apiRoutesProvider.provide(); return dependencies; } diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 9d5516c..f3a0bdc 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -16,282 +16,298 @@ const profileService = profileServiceProvider(ContactDetailsSet, NymSet); const router = express.Router(); -router.get('/signup/nostr-challenge', async (req, res) => { - const inviteUuid = req.cookies.inviteUuid; +class ApiRoutesProvider { + constructor() {} - let signUpChallenge; - try { - signUpChallenge = await invitesService.createSignUpChallenge(inviteUuid); - } catch (error) { - if (error instanceof errors.NotFoundError) { - return res.status(404).json({ - success: false, - message: 'Could not find invite with that id.', - }); - } + provide() { + router.get('/signup/nostr-challenge', async (req, res) => { + const inviteUuid = req.cookies.inviteUuid; - if (error instanceof errors.AlreadyUsedError) { - return res.status(410).json({ - success: false, - message: 'That invite has already been used.', - }); - } + let signUpChallenge; + try { + signUpChallenge = + await invitesService.createSignUpChallenge(inviteUuid); + } catch (error) { + if (error instanceof errors.NotFoundError) { + return res.status(404).json({ + success: false, + message: 'Could not find invite with that id.', + }); + } - return res.status(500).json({ - success: false, - message: 'Unexpected error.', - }); - } + if (error instanceof errors.AlreadyUsedError) { + return res.status(410).json({ + success: false, + message: 'That invite has already been used.', + }); + } - let relatedNostrChallenge; - try { - relatedNostrChallenge = await nostrService.getNostrChallenge( - signUpChallenge.nostr_challenge_uuid - ); - } catch (error) { - return res.status(500).json({ - success: false, - message: `Unexpected error: ${error}`, - }); - } - - return res.status(200).json({ challenge: relatedNostrChallenge.challenge }); -}); - -router.post('/signup/nostr-verify', async (req, res) => { - const signedEvent = req.body; - const sessionUuid = req.cookies.sessionUuid; - - let completedSignUpChallenge; - try { - completedSignUpChallenge = - await invitesService.verifySignUpChallenge(signedEvent); - } catch (error) { - if (error instanceof errors.ExpiredError) { - return res.status(410).json({ - success: false, - message: 'The challenge has expired, request a new one.', - }); - } - if (error instanceof errors.AlreadyUsedError) { - return res.status(410).json({ - success: false, - message: 'The challenge has been used, request a new one.', - }); - } - if (error instanceof errors.InvalidSignatureError) { - return res.status(400).json({ - success: false, - message: 'The challenge signature is not valid.', - }); - } - } - - await sessionService.relateSessionToPublicKey( - sessionUuid, - completedSignUpChallenge.public_key - ); - - return res.status(200).json({ success: true }); -}); - -router.get('/login/nostr-challenge', async (req, res) => { - let loginChallenge; - try { - loginChallenge = await loginService.createLoginChallenge(); - } catch (error) { - return res.status(500).json({ - success: false, - message: 'Unexpected error.', - }); - } - - let relatedNostrChallenge; - try { - relatedNostrChallenge = await nostrService.getNostrChallenge( - loginChallenge.nostr_challenge_uuid - ); - } catch (error) { - return res.status(500).json({ - success: false, - message: `Unexpected error: ${error}`, - }); - } - - return res.status(200).json({ challenge: relatedNostrChallenge.challenge }); -}); - -router.post('/login/nostr-verify', async (req, res) => { - const signedEvent = req.body; - const sessionUuid = req.cookies.sessionUuid; - - let completedLoginChallenge; - try { - completedLoginChallenge = - await loginService.verifyLoginChallenge(signedEvent); - } catch (error) { - console.log('helo5'); - console.log(error); - if (error instanceof errors.ExpiredError) { - return res.status(410).json({ - success: false, - message: 'The challenge has expired, request a new one.', - }); - } - if (error instanceof errors.AlreadyUsedError) { - return res.status(410).json({ - success: false, - message: 'The challenge has been used, request a new one.', - }); - } - if (error instanceof errors.InvalidSignatureError) { - return res.status(400).json({ - success: false, - message: 'The challenge signature is not valid.', - }); - } - if (error instanceof errors.ForbiddenError) { - console.log('helo?1'); - return res.status(403).json({ - success: false, - message: 'This public key is not authorized.', - }); - } - - return res.status(500).json({ - success: false, - message: 'Unexpected error.', - }); - } - console.log('helo?2'); - console.log(completedLoginChallenge); - await sessionService.relateSessionToPublicKey( - sessionUuid, - completedLoginChallenge.public_key - ); - - return res.status(200).json({ success: true }); -}); - -router.post( - '/set-contact-details', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - const encryptedContactDetails = req.body.encryptedContactDetails; - const publicKey = req.cookies.publicKey; - - if (!encryptedContactDetails) { - return res.status(400).json({ - success: false, - message: 'Missing contact details.', - }); - } - - await profileService.setContactDetails(publicKey, encryptedContactDetails); - - return res.status(200).json({ - success: true, - message: 'Contact details set successfully.', - }); - } -); - -router.post( - '/set-nym', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - const nym = req.body.nym; - const publicKey = req.cookies.publicKey; - - if (!nym) { - return res.status(400).json({ - success: false, - message: 'Missing nym', - }); - } - - await profileService.setNym(publicKey, nym); - - return res.status(200).json({ - success: true, - message: 'Nym set successfully.', - }); - } -); - -router.post( - '/offer', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - const publicKey = req.cookies.publicKey; - const offerDetails = req.body.offerDetails; - - await offerService.createOffer(publicKey, offerDetails); - - return res.status(200).json({ - success: true, - message: 'Offer created successfully', - }); - } -); - -router.delete( - '/offer/:offerUuid', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - const offerUuid = req.params.offerUuid; - - try { - await offerService.deleteOffer(offerUuid); - } catch (error) { - if (error instanceof errors.NotFoundError) { - return res.status(404).json({ + return res.status(500).json({ success: false, - message: 'Offer not found for the given public key.', + message: 'Unexpected error.', }); } - return res.status(500).json({ - success: false, - message: 'Unexpected error.', - }); - } - return res.status(204).json({ - success: true, - message: 'Offer deleted successfully', + let relatedNostrChallenge; + try { + relatedNostrChallenge = await nostrService.getNostrChallenge( + signUpChallenge.nostr_challenge_uuid + ); + } catch (error) { + return res.status(500).json({ + success: false, + message: `Unexpected error: ${error}`, + }); + } + + return res + .status(200) + .json({ challenge: relatedNostrChallenge.challenge }); }); + + router.post('/signup/nostr-verify', async (req, res) => { + const signedEvent = req.body; + const sessionUuid = req.cookies.sessionUuid; + + let completedSignUpChallenge; + try { + completedSignUpChallenge = + await invitesService.verifySignUpChallenge(signedEvent); + } catch (error) { + if (error instanceof errors.ExpiredError) { + return res.status(410).json({ + success: false, + message: 'The challenge has expired, request a new one.', + }); + } + if (error instanceof errors.AlreadyUsedError) { + return res.status(410).json({ + success: false, + message: 'The challenge has been used, request a new one.', + }); + } + if (error instanceof errors.InvalidSignatureError) { + return res.status(400).json({ + success: false, + message: 'The challenge signature is not valid.', + }); + } + } + + await sessionService.relateSessionToPublicKey( + sessionUuid, + completedSignUpChallenge.public_key + ); + + return res.status(200).json({ success: true }); + }); + + router.get('/login/nostr-challenge', async (req, res) => { + let loginChallenge; + try { + loginChallenge = await loginService.createLoginChallenge(); + } catch (error) { + return res.status(500).json({ + success: false, + message: 'Unexpected error.', + }); + } + + let relatedNostrChallenge; + try { + relatedNostrChallenge = await nostrService.getNostrChallenge( + loginChallenge.nostr_challenge_uuid + ); + } catch (error) { + return res.status(500).json({ + success: false, + message: `Unexpected error: ${error}`, + }); + } + + return res + .status(200) + .json({ challenge: relatedNostrChallenge.challenge }); + }); + + router.post('/login/nostr-verify', async (req, res) => { + const signedEvent = req.body; + const sessionUuid = req.cookies.sessionUuid; + + let completedLoginChallenge; + try { + completedLoginChallenge = + await loginService.verifyLoginChallenge(signedEvent); + } catch (error) { + console.log('helo5'); + console.log(error); + if (error instanceof errors.ExpiredError) { + return res.status(410).json({ + success: false, + message: 'The challenge has expired, request a new one.', + }); + } + if (error instanceof errors.AlreadyUsedError) { + return res.status(410).json({ + success: false, + message: 'The challenge has been used, request a new one.', + }); + } + if (error instanceof errors.InvalidSignatureError) { + return res.status(400).json({ + success: false, + message: 'The challenge signature is not valid.', + }); + } + if (error instanceof errors.ForbiddenError) { + console.log('helo?1'); + return res.status(403).json({ + success: false, + message: 'This public key is not authorized.', + }); + } + + return res.status(500).json({ + success: false, + message: 'Unexpected error.', + }); + } + console.log('helo?2'); + console.log(completedLoginChallenge); + await sessionService.relateSessionToPublicKey( + sessionUuid, + completedLoginChallenge.public_key + ); + + return res.status(200).json({ success: true }); + }); + + router.post( + '/set-contact-details', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const encryptedContactDetails = req.body.encryptedContactDetails; + const publicKey = req.cookies.publicKey; + + if (!encryptedContactDetails) { + return res.status(400).json({ + success: false, + message: 'Missing contact details.', + }); + } + + await profileService.setContactDetails( + publicKey, + encryptedContactDetails + ); + + return res.status(200).json({ + success: true, + message: 'Contact details set successfully.', + }); + } + ); + + router.post( + '/set-nym', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const nym = req.body.nym; + const publicKey = req.cookies.publicKey; + + if (!nym) { + return res.status(400).json({ + success: false, + message: 'Missing nym', + }); + } + + await profileService.setNym(publicKey, nym); + + return res.status(200).json({ + success: true, + message: 'Nym set successfully.', + }); + } + ); + + router.post( + '/offer', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const publicKey = req.cookies.publicKey; + const offerDetails = req.body.offerDetails; + + await offerService.createOffer(publicKey, offerDetails); + + return res.status(200).json({ + success: true, + message: 'Offer created successfully', + }); + } + ); + + router.delete( + '/offer/:offerUuid', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + const offerUuid = req.params.offerUuid; + + try { + await offerService.deleteOffer(offerUuid); + } catch (error) { + if (error instanceof errors.NotFoundError) { + return res.status(404).json({ + success: false, + message: 'Offer not found for the given public key.', + }); + } + return res.status(500).json({ + success: false, + message: 'Unexpected error.', + }); + } + + return res.status(204).json({ + success: true, + message: 'Offer deleted successfully', + }); + } + ); + + router.get( + '/publickey-offers', + rejectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + async (req, res) => { + console.log('elo'); + const publicKey = req.cookies.publicKey; + + const offers = await offerService.getOffersByPublicKey(publicKey); + + if (!offers) { + return res.status(404).json({ + success: true, + message: 'No offers posted by this public key.', + }); + } + + if (offers) { + return res.status(200).json({ + success: true, + message: 'Offers found', + data: offers, + }); + } + } + ); + + return router; } -); +} -router.get( - '/publickey-offers', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - async (req, res) => { - console.log('elo'); - const publicKey = req.cookies.publicKey; - - const offers = await offerService.getOffersByPublicKey(publicKey); - - if (!offers) { - return res.status(404).json({ - success: true, - message: 'No offers posted by this public key.', - }); - } - - if (offers) { - return res.status(200).json({ - success: true, - message: 'Offers found', - data: offers, - }); - } - } -); - -module.exports = router; +module.exports = ApiRoutesProvider; From 966d95149002881b9eb7a4e806a513cd47fb74c5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:07:57 +0100 Subject: [PATCH 127/239] pass in middlwares --- src/dependencies.js | 2 +- src/routes/apiRoutes.js | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index 4e44210..febcfc7 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -17,7 +17,7 @@ function buildDependencies() { dependencies.webRoutes = webRoutesProvider.provide(); const ApiRoutesProvider = require('./routes/apiRoutes'); - const apiRoutesProvider = new ApiRoutesProvider(); + const apiRoutesProvider = new ApiRoutesProvider(middlewares); dependencies.apiRoutes = apiRoutesProvider.provide(); return dependencies; } diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index f3a0bdc..7bfebb0 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -7,8 +7,6 @@ const sessionService = require('../services/sessionService'); const profileServiceProvider = require('../services/profileService'); const offerService = require('../services/offerService'); const errors = require('../errors'); -const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware'); -const rejectIfNotAuthorizedMiddleware = require('../middlewares/rejectIfNotAuthorizedMiddleware'); const ContactDetailsSet = require('../models/ContactDetailsSet'); const NymSet = require('../models/NymSet'); @@ -17,7 +15,9 @@ const profileService = profileServiceProvider(ContactDetailsSet, NymSet); const router = express.Router(); class ApiRoutesProvider { - constructor() {} + constructor(middlewares) { + this.middlewares = middlewares; + } provide() { router.get('/signup/nostr-challenge', async (req, res) => { @@ -184,8 +184,8 @@ class ApiRoutesProvider { router.post( '/set-contact-details', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, + this.middlewares.rejectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, async (req, res) => { const encryptedContactDetails = req.body.encryptedContactDetails; const publicKey = req.cookies.publicKey; @@ -211,8 +211,8 @@ class ApiRoutesProvider { router.post( '/set-nym', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, + this.middlewares.rejectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, async (req, res) => { const nym = req.body.nym; const publicKey = req.cookies.publicKey; @@ -235,8 +235,8 @@ class ApiRoutesProvider { router.post( '/offer', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, + this.middlewares.rejectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, async (req, res) => { const publicKey = req.cookies.publicKey; const offerDetails = req.body.offerDetails; @@ -252,8 +252,8 @@ class ApiRoutesProvider { router.delete( '/offer/:offerUuid', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, + this.middlewares.rejectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, async (req, res) => { const offerUuid = req.params.offerUuid; @@ -281,8 +281,8 @@ class ApiRoutesProvider { router.get( '/publickey-offers', - rejectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, + this.middlewares.rejectIfNotAuthorizedMiddleware, + this.middlewares.attachPublicKeyMiddleware, async (req, res) => { console.log('elo'); const publicKey = req.cookies.publicKey; From 54cb13d1e553c758d6515c919243f15498363776 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:13:39 +0100 Subject: [PATCH 128/239] revert the shit i did when i started the other way around --- ...directIfMissingProfileDetailsMiddleware.js | 5 +- src/routes/apiRoutes.js | 6 +- src/services/profileService.js | 59 +++++++++---------- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js index 090bc31..934048d 100644 --- a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js +++ b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js @@ -1,7 +1,4 @@ -const profileServiceProvider = require('../services/profileService'); -const ContactDetailsSet = require('../models/ContactDetailsSet'); -const NymSet = require('../models/NymSet'); -const profileService = profileServiceProvider(ContactDetailsSet, NymSet); +const profileService = require('../services/profileService'); async function redirectIfMissingProfileDetailsMiddleware(req, res, next) { const publicKey = req.cookies.publicKey; diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 7bfebb0..fc2ad86 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -4,14 +4,10 @@ const invitesService = require('../services/invitesService'); const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); const sessionService = require('../services/sessionService'); -const profileServiceProvider = require('../services/profileService'); +const profileService = require('../services/profileService'); const offerService = require('../services/offerService'); const errors = require('../errors'); -const ContactDetailsSet = require('../models/ContactDetailsSet'); -const NymSet = require('../models/NymSet'); -const profileService = profileServiceProvider(ContactDetailsSet, NymSet); - const router = express.Router(); class ApiRoutesProvider { diff --git a/src/services/profileService.js b/src/services/profileService.js index 2ea84c8..358499c 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -1,35 +1,34 @@ const uuid = require('uuid'); +const ContactDetailsSet = require('../models/ContactDetailsSet'); +const NymSet = require('../models/NymSet'); -const profileServicesProvider = (ContactDetailsSet, NymSet) => { - async function setContactDetails(publicKey, encryptedContactDetails) { - return await ContactDetailsSet.create({ - uuid: uuid.v7(), +async function setContactDetails(publicKey, encryptedContactDetails) { + return await ContactDetailsSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + encrypted_contact_details: encryptedContactDetails, + created_at: new Date().toISOString(), + }); +} + +async function setNym(publicKey, nym) { + return await NymSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + nym: nym, + created_at: new Date().toISOString(), + }); +} + +async function areProfileDetailsComplete(publicKey) { + const isNymSet = await NymSet.findOne({ where: { public_key: publicKey } }); + const areContactDetailsSet = await ContactDetailsSet.findOne({ + where: { public_key: publicKey, - encrypted_contact_details: encryptedContactDetails, - created_at: new Date().toISOString(), - }); - } + }, + }); - async function setNym(publicKey, nym) { - return await NymSet.create({ - uuid: uuid.v7(), - public_key: publicKey, - nym: nym, - created_at: new Date().toISOString(), - }); - } + return isNymSet && areContactDetailsSet; +} - async function areProfileDetailsComplete(publicKey) { - const isNymSet = await NymSet.findOne({ where: { public_key: publicKey } }); - const areContactDetailsSet = await ContactDetailsSet.findOne({ - where: { - public_key: publicKey, - }, - }); - - return isNymSet && areContactDetailsSet; - } - - return { setContactDetails, setNym, areProfileDetailsComplete }; -}; -module.exports = profileServicesProvider; +module.exports = { setContactDetails, setNym, areProfileDetailsComplete }; From 1cf303c31d49764187dd4e57ef6aa868f6a9814d Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:18:09 +0100 Subject: [PATCH 129/239] require all services in one go --- src/routes/apiRoutes.js | 30 +++++++++++++++++------------- src/services/index.js | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 src/services/index.js diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index fc2ad86..a02ae23 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -6,6 +6,8 @@ const loginService = require('../services/loginService'); const sessionService = require('../services/sessionService'); const profileService = require('../services/profileService'); const offerService = require('../services/offerService'); + +const services = require('../services'); const errors = require('../errors'); const router = express.Router(); @@ -13,6 +15,7 @@ const router = express.Router(); class ApiRoutesProvider { constructor(middlewares) { this.middlewares = middlewares; + this.services = services; } provide() { @@ -22,7 +25,7 @@ class ApiRoutesProvider { let signUpChallenge; try { signUpChallenge = - await invitesService.createSignUpChallenge(inviteUuid); + await services.invitesService.createSignUpChallenge(inviteUuid); } catch (error) { if (error instanceof errors.NotFoundError) { return res.status(404).json({ @@ -46,7 +49,7 @@ class ApiRoutesProvider { let relatedNostrChallenge; try { - relatedNostrChallenge = await nostrService.getNostrChallenge( + relatedNostrChallenge = await services.nostrService.getNostrChallenge( signUpChallenge.nostr_challenge_uuid ); } catch (error) { @@ -68,7 +71,7 @@ class ApiRoutesProvider { let completedSignUpChallenge; try { completedSignUpChallenge = - await invitesService.verifySignUpChallenge(signedEvent); + await services.invitesService.verifySignUpChallenge(signedEvent); } catch (error) { if (error instanceof errors.ExpiredError) { return res.status(410).json({ @@ -90,7 +93,7 @@ class ApiRoutesProvider { } } - await sessionService.relateSessionToPublicKey( + await services.sessionService.relateSessionToPublicKey( sessionUuid, completedSignUpChallenge.public_key ); @@ -101,7 +104,7 @@ class ApiRoutesProvider { router.get('/login/nostr-challenge', async (req, res) => { let loginChallenge; try { - loginChallenge = await loginService.createLoginChallenge(); + loginChallenge = await services.loginService.createLoginChallenge(); } catch (error) { return res.status(500).json({ success: false, @@ -111,7 +114,7 @@ class ApiRoutesProvider { let relatedNostrChallenge; try { - relatedNostrChallenge = await nostrService.getNostrChallenge( + relatedNostrChallenge = await services.nostrService.getNostrChallenge( loginChallenge.nostr_challenge_uuid ); } catch (error) { @@ -133,7 +136,7 @@ class ApiRoutesProvider { let completedLoginChallenge; try { completedLoginChallenge = - await loginService.verifyLoginChallenge(signedEvent); + await services.loginService.verifyLoginChallenge(signedEvent); } catch (error) { console.log('helo5'); console.log(error); @@ -170,7 +173,7 @@ class ApiRoutesProvider { } console.log('helo?2'); console.log(completedLoginChallenge); - await sessionService.relateSessionToPublicKey( + await services.sessionService.relateSessionToPublicKey( sessionUuid, completedLoginChallenge.public_key ); @@ -193,7 +196,7 @@ class ApiRoutesProvider { }); } - await profileService.setContactDetails( + await services.profileService.setContactDetails( publicKey, encryptedContactDetails ); @@ -220,7 +223,7 @@ class ApiRoutesProvider { }); } - await profileService.setNym(publicKey, nym); + await services.profileService.setNym(publicKey, nym); return res.status(200).json({ success: true, @@ -237,7 +240,7 @@ class ApiRoutesProvider { const publicKey = req.cookies.publicKey; const offerDetails = req.body.offerDetails; - await offerService.createOffer(publicKey, offerDetails); + await services.offerService.createOffer(publicKey, offerDetails); return res.status(200).json({ success: true, @@ -254,7 +257,7 @@ class ApiRoutesProvider { const offerUuid = req.params.offerUuid; try { - await offerService.deleteOffer(offerUuid); + await services.offerService.deleteOffer(offerUuid); } catch (error) { if (error instanceof errors.NotFoundError) { return res.status(404).json({ @@ -283,7 +286,8 @@ class ApiRoutesProvider { console.log('elo'); const publicKey = req.cookies.publicKey; - const offers = await offerService.getOffersByPublicKey(publicKey); + const offers = + await services.offerService.getOffersByPublicKey(publicKey); if (!offers) { return res.status(404).json({ diff --git a/src/services/index.js b/src/services/index.js new file mode 100644 index 0000000..3e35e60 --- /dev/null +++ b/src/services/index.js @@ -0,0 +1,15 @@ +const invitesService = require('../services/invitesService'); +const nostrService = require('../services/nostrService'); +const loginService = require('../services/loginService'); +const sessionService = require('../services/sessionService'); +const profileService = require('../services/profileService'); +const offerService = require('../services/offerService'); + +module.exports = { + invitesService, + nostrService, + loginService, + sessionService, + profileService, + offerService, +}; From 47b2821f9068ae4f1f05b5175fd6c4030bd00f52 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:21:37 +0100 Subject: [PATCH 130/239] passing services --- src/dependencies.js | 6 +++--- src/routes/apiRoutes.js | 47 ++++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index febcfc7..5ba1f36 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -6,18 +6,18 @@ function buildDependencies() { const middlewares = require('./middlewares'); dependencies.middlewares = middlewares; - const invitesService = require('./services/invitesService'); + const services = require('./services'); const WebRoutesProvider = require('./routes/webRoutes'); const webRoutesProvider = new WebRoutesProvider({ express, middlewares, - invitesService, + invitesService: services.invitesService, }); dependencies.webRoutes = webRoutesProvider.provide(); const ApiRoutesProvider = require('./routes/apiRoutes'); - const apiRoutesProvider = new ApiRoutesProvider(middlewares); + const apiRoutesProvider = new ApiRoutesProvider({ middlewares, services }); dependencies.apiRoutes = apiRoutesProvider.provide(); return dependencies; } diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index a02ae23..7a86bdb 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -1,19 +1,11 @@ const express = require('express'); -const invitesService = require('../services/invitesService'); -const nostrService = require('../services/nostrService'); -const loginService = require('../services/loginService'); -const sessionService = require('../services/sessionService'); -const profileService = require('../services/profileService'); -const offerService = require('../services/offerService'); - -const services = require('../services'); const errors = require('../errors'); const router = express.Router(); class ApiRoutesProvider { - constructor(middlewares) { + constructor({ middlewares, services }) { this.middlewares = middlewares; this.services = services; } @@ -25,7 +17,7 @@ class ApiRoutesProvider { let signUpChallenge; try { signUpChallenge = - await services.invitesService.createSignUpChallenge(inviteUuid); + await this.services.invitesService.createSignUpChallenge(inviteUuid); } catch (error) { if (error instanceof errors.NotFoundError) { return res.status(404).json({ @@ -49,9 +41,10 @@ class ApiRoutesProvider { let relatedNostrChallenge; try { - relatedNostrChallenge = await services.nostrService.getNostrChallenge( - signUpChallenge.nostr_challenge_uuid - ); + relatedNostrChallenge = + await this.services.nostrService.getNostrChallenge( + signUpChallenge.nostr_challenge_uuid + ); } catch (error) { return res.status(500).json({ success: false, @@ -71,7 +64,7 @@ class ApiRoutesProvider { let completedSignUpChallenge; try { completedSignUpChallenge = - await services.invitesService.verifySignUpChallenge(signedEvent); + await this.services.invitesService.verifySignUpChallenge(signedEvent); } catch (error) { if (error instanceof errors.ExpiredError) { return res.status(410).json({ @@ -93,7 +86,7 @@ class ApiRoutesProvider { } } - await services.sessionService.relateSessionToPublicKey( + await this.services.sessionService.relateSessionToPublicKey( sessionUuid, completedSignUpChallenge.public_key ); @@ -104,7 +97,8 @@ class ApiRoutesProvider { router.get('/login/nostr-challenge', async (req, res) => { let loginChallenge; try { - loginChallenge = await services.loginService.createLoginChallenge(); + loginChallenge = + await this.services.loginService.createLoginChallenge(); } catch (error) { return res.status(500).json({ success: false, @@ -114,9 +108,10 @@ class ApiRoutesProvider { let relatedNostrChallenge; try { - relatedNostrChallenge = await services.nostrService.getNostrChallenge( - loginChallenge.nostr_challenge_uuid - ); + relatedNostrChallenge = + await this.services.nostrService.getNostrChallenge( + loginChallenge.nostr_challenge_uuid + ); } catch (error) { return res.status(500).json({ success: false, @@ -136,7 +131,7 @@ class ApiRoutesProvider { let completedLoginChallenge; try { completedLoginChallenge = - await services.loginService.verifyLoginChallenge(signedEvent); + await this.services.loginService.verifyLoginChallenge(signedEvent); } catch (error) { console.log('helo5'); console.log(error); @@ -173,7 +168,7 @@ class ApiRoutesProvider { } console.log('helo?2'); console.log(completedLoginChallenge); - await services.sessionService.relateSessionToPublicKey( + await this.services.sessionService.relateSessionToPublicKey( sessionUuid, completedLoginChallenge.public_key ); @@ -196,7 +191,7 @@ class ApiRoutesProvider { }); } - await services.profileService.setContactDetails( + await this.services.profileService.setContactDetails( publicKey, encryptedContactDetails ); @@ -223,7 +218,7 @@ class ApiRoutesProvider { }); } - await services.profileService.setNym(publicKey, nym); + await this.services.profileService.setNym(publicKey, nym); return res.status(200).json({ success: true, @@ -240,7 +235,7 @@ class ApiRoutesProvider { const publicKey = req.cookies.publicKey; const offerDetails = req.body.offerDetails; - await services.offerService.createOffer(publicKey, offerDetails); + await this.services.offerService.createOffer(publicKey, offerDetails); return res.status(200).json({ success: true, @@ -257,7 +252,7 @@ class ApiRoutesProvider { const offerUuid = req.params.offerUuid; try { - await services.offerService.deleteOffer(offerUuid); + await this.services.offerService.deleteOffer(offerUuid); } catch (error) { if (error instanceof errors.NotFoundError) { return res.status(404).json({ @@ -287,7 +282,7 @@ class ApiRoutesProvider { const publicKey = req.cookies.publicKey; const offers = - await services.offerService.getOffersByPublicKey(publicKey); + await this.services.offerService.getOffersByPublicKey(publicKey); if (!offers) { return res.status(404).json({ From 2f93e658625915ce7b6333438588884bd73c6c4c Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:23:36 +0100 Subject: [PATCH 131/239] pass errors --- src/dependencies.js | 7 ++++++- src/routes/apiRoutes.js | 25 ++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index 5ba1f36..d4f71b8 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -7,6 +7,7 @@ function buildDependencies() { dependencies.middlewares = middlewares; const services = require('./services'); + const errors = require('./errors'); const WebRoutesProvider = require('./routes/webRoutes'); const webRoutesProvider = new WebRoutesProvider({ @@ -17,7 +18,11 @@ function buildDependencies() { dependencies.webRoutes = webRoutesProvider.provide(); const ApiRoutesProvider = require('./routes/apiRoutes'); - const apiRoutesProvider = new ApiRoutesProvider({ middlewares, services }); + const apiRoutesProvider = new ApiRoutesProvider({ + middlewares, + services, + errors, + }); dependencies.apiRoutes = apiRoutesProvider.provide(); return dependencies; } diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 7a86bdb..d873438 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -1,13 +1,12 @@ const express = require('express'); -const errors = require('../errors'); - const router = express.Router(); class ApiRoutesProvider { - constructor({ middlewares, services }) { + constructor({ middlewares, services, errors }) { this.middlewares = middlewares; this.services = services; + this.errors = errors; } provide() { @@ -19,14 +18,14 @@ class ApiRoutesProvider { signUpChallenge = await this.services.invitesService.createSignUpChallenge(inviteUuid); } catch (error) { - if (error instanceof errors.NotFoundError) { + if (error instanceof this.services.NotFoundError) { return res.status(404).json({ success: false, message: 'Could not find invite with that id.', }); } - if (error instanceof errors.AlreadyUsedError) { + if (error instanceof this.errors.AlreadyUsedError) { return res.status(410).json({ success: false, message: 'That invite has already been used.', @@ -66,19 +65,19 @@ class ApiRoutesProvider { completedSignUpChallenge = await this.services.invitesService.verifySignUpChallenge(signedEvent); } catch (error) { - if (error instanceof errors.ExpiredError) { + if (error instanceof this.errors.ExpiredError) { return res.status(410).json({ success: false, message: 'The challenge has expired, request a new one.', }); } - if (error instanceof errors.AlreadyUsedError) { + if (error instanceof this.errors.AlreadyUsedError) { return res.status(410).json({ success: false, message: 'The challenge has been used, request a new one.', }); } - if (error instanceof errors.InvalidSignatureError) { + if (error instanceof this.errors.InvalidSignatureError) { return res.status(400).json({ success: false, message: 'The challenge signature is not valid.', @@ -135,25 +134,25 @@ class ApiRoutesProvider { } catch (error) { console.log('helo5'); console.log(error); - if (error instanceof errors.ExpiredError) { + if (error instanceof this.errors.ExpiredError) { return res.status(410).json({ success: false, message: 'The challenge has expired, request a new one.', }); } - if (error instanceof errors.AlreadyUsedError) { + if (error instanceof this.errors.AlreadyUsedError) { return res.status(410).json({ success: false, message: 'The challenge has been used, request a new one.', }); } - if (error instanceof errors.InvalidSignatureError) { + if (error instanceof this.errors.InvalidSignatureError) { return res.status(400).json({ success: false, message: 'The challenge signature is not valid.', }); } - if (error instanceof errors.ForbiddenError) { + if (error instanceof this.errors.ForbiddenError) { console.log('helo?1'); return res.status(403).json({ success: false, @@ -254,7 +253,7 @@ class ApiRoutesProvider { try { await this.services.offerService.deleteOffer(offerUuid); } catch (error) { - if (error instanceof errors.NotFoundError) { + if (error instanceof this.errors.NotFoundError) { return res.status(404).json({ success: false, message: 'Offer not found for the given public key.', From 1a82b287454fcc2a08623c7f1e01fbed24f7e126 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:29:51 +0100 Subject: [PATCH 132/239] pass in express --- src/dependencies.js | 1 + src/routes/apiRoutes.js | 28 ++++++++++++---------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index d4f71b8..b182680 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -19,6 +19,7 @@ function buildDependencies() { const ApiRoutesProvider = require('./routes/apiRoutes'); const apiRoutesProvider = new ApiRoutesProvider({ + express, middlewares, services, errors, diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index d873438..6979b53 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -1,16 +1,13 @@ -const express = require('express'); - -const router = express.Router(); - class ApiRoutesProvider { - constructor({ middlewares, services, errors }) { + constructor({ express, middlewares, services, errors }) { + this.router = express.Router(); this.middlewares = middlewares; this.services = services; this.errors = errors; } provide() { - router.get('/signup/nostr-challenge', async (req, res) => { + this.router.get('/signup/nostr-challenge', async (req, res) => { const inviteUuid = req.cookies.inviteUuid; let signUpChallenge; @@ -56,7 +53,7 @@ class ApiRoutesProvider { .json({ challenge: relatedNostrChallenge.challenge }); }); - router.post('/signup/nostr-verify', async (req, res) => { + this.router.post('/signup/nostr-verify', async (req, res) => { const signedEvent = req.body; const sessionUuid = req.cookies.sessionUuid; @@ -93,7 +90,7 @@ class ApiRoutesProvider { return res.status(200).json({ success: true }); }); - router.get('/login/nostr-challenge', async (req, res) => { + this.router.get('/login/nostr-challenge', async (req, res) => { let loginChallenge; try { loginChallenge = @@ -123,7 +120,7 @@ class ApiRoutesProvider { .json({ challenge: relatedNostrChallenge.challenge }); }); - router.post('/login/nostr-verify', async (req, res) => { + this.router.post('/login/nostr-verify', async (req, res) => { const signedEvent = req.body; const sessionUuid = req.cookies.sessionUuid; @@ -175,7 +172,7 @@ class ApiRoutesProvider { return res.status(200).json({ success: true }); }); - router.post( + this.router.post( '/set-contact-details', this.middlewares.rejectIfNotAuthorizedMiddleware, this.middlewares.attachPublicKeyMiddleware, @@ -202,7 +199,7 @@ class ApiRoutesProvider { } ); - router.post( + this.router.post( '/set-nym', this.middlewares.rejectIfNotAuthorizedMiddleware, this.middlewares.attachPublicKeyMiddleware, @@ -226,7 +223,7 @@ class ApiRoutesProvider { } ); - router.post( + this.router.post( '/offer', this.middlewares.rejectIfNotAuthorizedMiddleware, this.middlewares.attachPublicKeyMiddleware, @@ -243,7 +240,7 @@ class ApiRoutesProvider { } ); - router.delete( + this.router.delete( '/offer/:offerUuid', this.middlewares.rejectIfNotAuthorizedMiddleware, this.middlewares.attachPublicKeyMiddleware, @@ -272,7 +269,7 @@ class ApiRoutesProvider { } ); - router.get( + this.router.get( '/publickey-offers', this.middlewares.rejectIfNotAuthorizedMiddleware, this.middlewares.attachPublicKeyMiddleware, @@ -300,8 +297,7 @@ class ApiRoutesProvider { } ); - return router; + return this.router; } } - module.exports = ApiRoutesProvider; From 1702f2998789bbb1785d3e04b11dbaa5b16f03d8 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:40:54 +0100 Subject: [PATCH 133/239] typo --- src/routes/apiRoutes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 6979b53..9ff1513 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -15,7 +15,7 @@ class ApiRoutesProvider { signUpChallenge = await this.services.invitesService.createSignUpChallenge(inviteUuid); } catch (error) { - if (error instanceof this.services.NotFoundError) { + if (error instanceof this.errors.NotFoundError) { return res.status(404).json({ success: false, message: 'Could not find invite with that id.', From 011a255d9edc00025fa914c1bfaf7272e381e3d5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:44:02 +0100 Subject: [PATCH 134/239] root module def for models --- src/models/index.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/models/index.js diff --git a/src/models/index.js b/src/models/index.js new file mode 100644 index 0000000..ed7ed30 --- /dev/null +++ b/src/models/index.js @@ -0,0 +1,31 @@ +const AppInviteCreated = require('./AppInviteCreated'); +const ContactDetailsSet = require('./ContactDetailsSet'); +const LoginChallengeCompleted = require('./LoginChallengeCompleted'); +const LoginChallengeCreated = require('./LoginChallengeCreated'); +const NostrChallengeCompleted = require('./NostrChallengeCompleted'); +const NostrChallengeCreated = require('./NostrChallengeCreated'); +const NymSet = require('./NymSet'); +const OfferCreated = require('./OfferCreated'); +const OfferDeleted = require('./OfferDeleted'); +const OfferDetailsSet = require('./OfferDetailsSet'); +const SessionCreated = require('./SessionCreated'); +const SessionRelatedToPublickey = require('./SessionRelatedToPublickey'); +const SignUpChallengeCompleted = require('./SignUpChallengeCompleted'); +const SignUpChallengeCreated = require('./SignUpChallengeCreated'); + +module.exports = { + SignUpChallengeCreated, + SignUpChallengeCompleted, + SessionRelatedToPublickey, + SessionCreated, + OfferDeleted, + OfferDetailsSet, + OfferCreated, + NymSet, + NostrChallengeCreated, + NostrChallengeCompleted, + LoginChallengeCompleted, + LoginChallengeCreated, + ContactDetailsSet, + AppInviteCreated, +}; From d34e62070a8449f215e718371e254e26ae7b19eb Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 5 Mar 2025 16:49:03 +0100 Subject: [PATCH 135/239] refactor model imports in all services --- src/services/invitesService.js | 53 +++++++++++++++++----------------- src/services/loginService.js | 7 ++--- src/services/nostrService.js | 15 +++++----- src/services/offerService.js | 18 +++++------- src/services/profileService.js | 14 +++++---- src/services/sessionService.js | 31 ++++++++++---------- 6 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/services/invitesService.js b/src/services/invitesService.js index 432633c..16d7805 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -1,14 +1,11 @@ const uuid = require('uuid'); const nostrService = require('./nostrService'); -const AppInviteCreated = require('../models/AppInviteCreated'); -const SignUpChallengeCreated = require('../models/SignUpChallengeCreated'); -const SignUpChallengeCompleted = require('../models/SignUpChallengeCompleted'); - +const models = require('../models'); const errors = require('../errors'); async function appInviteExists(inviteUuid) { - const invite = await AppInviteCreated.findOne({ + const invite = await models.AppInviteCreated.findOne({ where: { uuid: inviteUuid }, }); if (invite) { @@ -18,18 +15,19 @@ async function appInviteExists(inviteUuid) { } async function getAppInvite(inviteUuid) { - const invite = await AppInviteCreated.findOne({ + const invite = await models.AppInviteCreated.findOne({ where: { uuid: inviteUuid }, }); return invite; } async function isAppInviteSpent(appInviteUuid) { - const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({ - where: { - app_invite_uuid: appInviteUuid, - }, - }); + const signUpChallengeCompleted = + await models.SignUpChallengeCompleted.findOne({ + where: { + app_invite_uuid: appInviteUuid, + }, + }); if (signUpChallengeCompleted) { return true; @@ -38,7 +36,7 @@ async function isAppInviteSpent(appInviteUuid) { } async function createAppInvite(inviterPubKey) { - return await AppInviteCreated.create({ + return await models.AppInviteCreated.create({ uuid: uuid.v7(), inviter_pub_key: inviterPubKey, created_at: new Date().toISOString(), @@ -56,7 +54,7 @@ async function createSignUpChallenge(appInviteUuid) { const nostrChallenge = await nostrService.createNostrChallenge(); - return await SignUpChallengeCreated.create({ + return await models.SignUpChallengeCreated.create({ uuid: uuid.v7(), nostr_challenge_uuid: nostrChallenge.uuid, app_invite_uuid: appInviteUuid, @@ -70,7 +68,7 @@ async function verifySignUpChallenge(signedEvent) { const nostrChallenge = await nostrService.getNostrChallenge(null, challenge); - const signUpChallenge = await SignUpChallengeCreated.findOne({ + const signUpChallenge = await models.SignUpChallengeCreated.findOne({ where: { nostr_challenge_uuid: nostrChallenge.uuid, }, @@ -83,23 +81,26 @@ async function verifySignUpChallenge(signedEvent) { const completedNostrChallenge = await nostrService.verifyNostrChallenge(signedEvent); - const completedSignUpChallenge = await SignUpChallengeCompleted.create({ - uuid: uuid.v7(), - nostr_challenge_completed_uuid: completedNostrChallenge.uuid, - app_invite_uuid: signUpChallenge.app_invite_uuid, - public_key: completedNostrChallenge.public_key, - created_at: new Date().toISOString(), - }); + const completedSignUpChallenge = await models.SignUpChallengeCompleted.create( + { + uuid: uuid.v7(), + nostr_challenge_completed_uuid: completedNostrChallenge.uuid, + app_invite_uuid: signUpChallenge.app_invite_uuid, + public_key: completedNostrChallenge.public_key, + created_at: new Date().toISOString(), + } + ); return completedSignUpChallenge; } async function isPublicKeySignedUp(publicKey) { - const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({ - where: { - public_key: publicKey, - }, - }); + const signUpChallengeCompleted = + await models.SignUpChallengeCompleted.findOne({ + where: { + public_key: publicKey, + }, + }); if (signUpChallengeCompleted) { return true; diff --git a/src/services/loginService.js b/src/services/loginService.js index 9d5f973..3de0090 100644 --- a/src/services/loginService.js +++ b/src/services/loginService.js @@ -2,15 +2,14 @@ const uuid = require('uuid'); const nostrService = require('./nostrService'); const invitesService = require('./invitesService'); -const LoginChallengeCreated = require('../models/LoginChallengeCreated'); -const LoginChallengeCompleted = require('../models/LoginChallengeCompleted'); +const models = require('../models'); const errors = require('../errors'); async function createLoginChallenge() { const nostrChallenge = await nostrService.createNostrChallenge(); - return await LoginChallengeCreated.create({ + return await models.LoginChallengeCreated.create({ uuid: uuid.v7(), nostr_challenge_uuid: nostrChallenge.uuid, created_at: new Date().toISOString(), @@ -39,7 +38,7 @@ async function verifyLoginChallenge(signedEvent) { ); } - const completedLoginChallenge = await LoginChallengeCompleted.create({ + const completedLoginChallenge = await models.LoginChallengeCompleted.create({ uuid: uuid.v7(), nostr_challenge_completed_uuid: completedNostrChallenge.uuid, public_key: completedNostrChallenge.public_key, diff --git a/src/services/nostrService.js b/src/services/nostrService.js index 542ba1c..412d0ba 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -3,8 +3,7 @@ const crypto = require('crypto'); const { Op, TimeoutError } = require('sequelize'); const { verifyEvent } = require('nostr-tools'); -const NostrChallengeCreated = require('../models/NostrChallengeCreated'); -const NostrChallengeCompleted = require('../models/NostrChallengeCompleted'); +const models = require('../models'); const constants = require('../constants'); const errors = require('../errors'); @@ -17,7 +16,7 @@ async function createNostrChallenge() { constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS ); - const nostrChallenge = await NostrChallengeCreated.create({ + const nostrChallenge = await models.NostrChallengeCreated.create({ uuid: uuid.v7(), challenge: crypto.randomBytes(32).toString('hex'), expires_at: expiryTimestamp.toISOString(), @@ -29,7 +28,7 @@ async function createNostrChallenge() { async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) { if (nostrChallengeUuid) { - return await NostrChallengeCreated.findOne({ + return await models.NostrChallengeCreated.findOne({ where: { uuid: nostrChallengeUuid, }, @@ -37,7 +36,7 @@ async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) { } if (challenge) { - return await NostrChallengeCreated.findOne({ + return await models.NostrChallengeCreated.findOne({ where: { challenge, }, @@ -66,7 +65,7 @@ async function verifyNostrChallenge(signedEvent) { throw new errors.InvalidSignatureError('Signature is not valid.'); } - return await NostrChallengeCompleted.create({ + return await models.NostrChallengeCompleted.create({ uuid: uuid.v7(), challenge: challenge, signed_event: signedEvent, @@ -76,7 +75,7 @@ async function verifyNostrChallenge(signedEvent) { } async function isNostrChallengeFresh(challengeString) { - const nostrChallenge = await NostrChallengeCreated.findOne({ + const nostrChallenge = await models.NostrChallengeCreated.findOne({ where: { challenge: challengeString, expires_at: { @@ -92,7 +91,7 @@ async function isNostrChallengeFresh(challengeString) { } async function hasNostrChallengeBeenCompleted(challengeString) { - const completedNostrChallenge = await NostrChallengeCompleted.findOne({ + const completedNostrChallenge = await models.NostrChallengeCompleted.findOne({ where: { challenge: challengeString, }, diff --git a/src/services/offerService.js b/src/services/offerService.js index fe6273d..ab1f19e 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -1,19 +1,17 @@ const uuid = require('uuid'); -const OfferCreated = require('../models/OfferCreated'); -const OfferDetailsSet = require('../models/OfferDetailsSet'); -const OfferDeleted = require('../models/OfferDeleted'); +const models = require('../models'); const errors = require('../errors'); async function createOffer(publicKey, offerDetails) { - const offerCreated = await OfferCreated.create({ + const offerCreated = await models.OfferCreated.create({ uuid: uuid.v7(), public_key: publicKey, created_at: new Date().toISOString(), }); - await OfferDetailsSet.create({ + await models.OfferDetailsSet.create({ uuid: uuid.v7(), offer_uuid: offerCreated.uuid, wants: offerDetails.wants, @@ -33,17 +31,17 @@ async function createOffer(publicKey, offerDetails) { async function deleteOffer(offerUuid) { const offerExists = Boolean( - await OfferCreated.findOne({ where: { uuid: offerUuid } }) + await models.OfferCreated.findOne({ where: { uuid: offerUuid } }) ); const offerHasBeenDeleted = Boolean( - await OfferDeleted.findOne({ where: { offer_uuid: offerUuid } }) + await models.OfferDeleted.findOne({ where: { offer_uuid: offerUuid } }) ); if (!offerExists || offerHasBeenDeleted) { throw new errors.NotFoundError(`Could not find the offer.`); } - return OfferDeleted.create({ + return models.OfferDeleted.create({ uuid: uuid.v7(), offer_uuid: offerUuid, created_at: new Date().toISOString(), @@ -51,7 +49,7 @@ async function deleteOffer(offerUuid) { } async function getOffersByPublicKey(publicKey) { - const offers = await OfferCreated.findAll({ + const offers = await models.OfferCreated.findAll({ where: { public_key: publicKey, }, @@ -66,7 +64,7 @@ async function getOffersByPublicKey(publicKey) { const offersToReturn = []; if (offers) { for (const someOffer of offers) { - const offerDetails = await OfferDetailsSet.findOne({ + const offerDetails = await models.OfferDetailsSet.findOne({ where: { offer_uuid: someOffer.uuid, }, diff --git a/src/services/profileService.js b/src/services/profileService.js index 358499c..a3013b0 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -1,9 +1,9 @@ const uuid = require('uuid'); -const ContactDetailsSet = require('../models/ContactDetailsSet'); -const NymSet = require('../models/NymSet'); + +const models = require('../models'); async function setContactDetails(publicKey, encryptedContactDetails) { - return await ContactDetailsSet.create({ + return await models.ContactDetailsSet.create({ uuid: uuid.v7(), public_key: publicKey, encrypted_contact_details: encryptedContactDetails, @@ -12,7 +12,7 @@ async function setContactDetails(publicKey, encryptedContactDetails) { } async function setNym(publicKey, nym) { - return await NymSet.create({ + return await models.NymSet.create({ uuid: uuid.v7(), public_key: publicKey, nym: nym, @@ -21,8 +21,10 @@ async function setNym(publicKey, nym) { } async function areProfileDetailsComplete(publicKey) { - const isNymSet = await NymSet.findOne({ where: { public_key: publicKey } }); - const areContactDetailsSet = await ContactDetailsSet.findOne({ + const isNymSet = await models.NymSet.findOne({ + where: { public_key: publicKey }, + }); + const areContactDetailsSet = await models.ContactDetailsSet.findOne({ where: { public_key: publicKey, }, diff --git a/src/services/sessionService.js b/src/services/sessionService.js index 452f390..fdf20fc 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -1,7 +1,6 @@ const uuid = require('uuid'); -const SessionCreated = require('../models/SessionCreated'); -const SessionRelatedToPublickey = require('../models/SessionRelatedToPublickey'); +const models = require('../models'); const invitesService = require('./invitesService'); const constants = require('../constants'); @@ -13,7 +12,7 @@ async function createSession(sessionUuid) { expiryTimestamp.getSeconds() + constants.DEFAULT_SESSION_DURATION_SECONDS ); - return await SessionCreated.create({ + return await models.SessionCreated.create({ uuid: sessionUuid, created_at: currentTimestamp.toISOString(), expires_at: expiryTimestamp.toISOString(), @@ -21,7 +20,7 @@ async function createSession(sessionUuid) { } async function isSessionValid(sessionUuid) { - const currentSession = await SessionCreated.findOne({ + const currentSession = await models.SessionCreated.findOne({ where: { uuid: sessionUuid, }, @@ -47,7 +46,7 @@ async function relateSessionToPublicKey(sessionUuid, publicKey) { throw Error('Public key is not signed up.'); } - return SessionRelatedToPublickey.create({ + return models.SessionRelatedToPublickey.create({ uuid: uuid.v7(), session_uuid: sessionUuid, public_key: publicKey, @@ -56,11 +55,12 @@ async function relateSessionToPublicKey(sessionUuid, publicKey) { } async function isSessionAuthorized(sessionUuid) { - const isSessionRelatedToPublicKey = await SessionRelatedToPublickey.findOne({ - where: { - session_uuid: sessionUuid, - }, - }); + const isSessionRelatedToPublicKey = + await models.SessionRelatedToPublickey.findOne({ + where: { + session_uuid: sessionUuid, + }, + }); if (isSessionRelatedToPublicKey) { return true; @@ -70,11 +70,12 @@ async function isSessionAuthorized(sessionUuid) { } async function getPublicKeyRelatedToSession(sessionUuid) { - const sessionRelatedToPublickey = await SessionRelatedToPublickey.findOne({ - where: { - session_uuid: sessionUuid, - }, - }); + const sessionRelatedToPublickey = + await models.SessionRelatedToPublickey.findOne({ + where: { + session_uuid: sessionUuid, + }, + }); if (sessionRelatedToPublickey) { return sessionRelatedToPublickey.public_key; From 3c5aa812abac573942d214d56e606612d5c48842 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 00:14:30 +0100 Subject: [PATCH 136/239] refactor first middleware --- src/middlewares/attachPublicKeyMiddleware.js | 28 ++++++++++++-------- src/middlewares/index.js | 7 ++++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/middlewares/attachPublicKeyMiddleware.js b/src/middlewares/attachPublicKeyMiddleware.js index c6ad835..4a60104 100644 --- a/src/middlewares/attachPublicKeyMiddleware.js +++ b/src/middlewares/attachPublicKeyMiddleware.js @@ -1,14 +1,20 @@ -const sessionService = require('../services/sessionService'); - -async function attachPublicKeyMiddleware(req, res, next) { - const publicKey = await sessionService.getPublicKeyRelatedToSession( - req.cookies.sessionUuid - ); - - if (publicKey) { - req.cookies.publicKey = publicKey; +class AttachPublicKeyMiddlewareProvider { + constructor(sessionService) { + this.sessionService = sessionService; + } + + provide() { + return async (req, res, next) => { + const publicKey = await this.sessionService.getPublicKeyRelatedToSession( + req.cookies.sessionUuid + ); + + if (publicKey) { + req.cookies.publicKey = publicKey; + } + next(); + }; } - next(); } -module.exports = attachPublicKeyMiddleware; +module.exports = AttachPublicKeyMiddlewareProvider; diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 6a56fa8..289f72e 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,10 +1,15 @@ const redirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); -const attachPublicKeyMiddleware = require('./attachPublicKeyMiddleware'); const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); const redirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); const rejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); const createSessionMiddleware = require('./createSessionMiddleware'); +const sessionService = require('../services/sessionService'); +const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); +const attachPublicKeyMiddleware = new AttachPublicKeyMiddlewareProvider( + sessionService +).provide(); + module.exports = { redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, From 72b68e772b1942b7fdc48d54d4ccc3af5f069407 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 00:30:28 +0100 Subject: [PATCH 137/239] another --- src/middlewares/createSessionMiddleware.js | 56 ++++++++++++---------- src/middlewares/index.js | 8 +++- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/middlewares/createSessionMiddleware.js b/src/middlewares/createSessionMiddleware.js index 8d181d6..53e15dd 100644 --- a/src/middlewares/createSessionMiddleware.js +++ b/src/middlewares/createSessionMiddleware.js @@ -1,33 +1,39 @@ const uuid = require('uuid'); -const sessionService = require('../services/sessionService'); -const constants = require('../constants'); - -async function setAndPersistNewSession(res) { - const sessionUuid = uuid.v7(); - res.cookie('sessionUuid', sessionUuid, { - httpOnly: true, - maxAge: constants.DEFAULT_SESSION_DURATION_SECONDS * 1000, - }); - return await sessionService.createSession(sessionUuid); -} - -async function createSessionMiddleware(req, res, next) { - const sessionUuid = req.cookies.sessionUuid; - - if (!sessionUuid) { - const newSession = await setAndPersistNewSession(res); - req.cookies.sessionUuid = newSession.uuid; +class CreateSessionMiddlewareProvider { + constructor({ constants, sessionService }) { + this.constants = constants; + this.sessionService = sessionService; } - if (sessionUuid) { - if (!(await sessionService.isSessionValid(sessionUuid))) { - const newSession = await setAndPersistNewSession(res); - req.cookies.sessionUuid = newSession.uuid; - } + provide() { + return async (req, res, next) => { + const sessionUuid = req.cookies.sessionUuid; + + if (!sessionUuid) { + const newSession = await this.setAndPersistNewSession(res); + req.cookies.sessionUuid = newSession.uuid; + } + + if (sessionUuid) { + if (!(await this.sessionService.isSessionValid(sessionUuid))) { + const newSession = await this.setAndPersistNewSession(res); + req.cookies.sessionUuid = newSession.uuid; + } + } + + next(); + }; } - next(); + async setAndPersistNewSession(res) { + const sessionUuid = uuid.v7(); + res.cookie('sessionUuid', sessionUuid, { + httpOnly: true, + maxAge: this.constants.DEFAULT_SESSION_DURATION_SECONDS * 1000, + }); + return await this.sessionService.createSession(sessionUuid); + } } -module.exports = createSessionMiddleware; +module.exports = CreateSessionMiddlewareProvider; diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 289f72e..8ad10ef 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -2,7 +2,6 @@ const redirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMidd const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); const redirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); const rejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); -const createSessionMiddleware = require('./createSessionMiddleware'); const sessionService = require('../services/sessionService'); const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); @@ -10,6 +9,13 @@ const attachPublicKeyMiddleware = new AttachPublicKeyMiddlewareProvider( sessionService ).provide(); +const constants = require('../constants'); +const CreateSessionMiddlewareProvider = require('./createSessionMiddleware'); +const createSessionMiddleware = new CreateSessionMiddlewareProvider({ + constants, + sessionService, +}).provide(); + module.exports = { redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, From 1ec83c5e5dbbc83544889ca667b68aac984efcae Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 00:33:15 +0100 Subject: [PATCH 138/239] another --- src/middlewares/index.js | 6 +++- .../rejectIfNotAuthorizedMiddleware.js | 30 ++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 8ad10ef..4a13843 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,7 +1,6 @@ const redirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); const redirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); -const rejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); const sessionService = require('../services/sessionService'); const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); @@ -16,6 +15,11 @@ const createSessionMiddleware = new CreateSessionMiddlewareProvider({ sessionService, }).provide(); +const RejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); +const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware( + sessionService +).provide(); + module.exports = { redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, diff --git a/src/middlewares/rejectIfNotAuthorizedMiddleware.js b/src/middlewares/rejectIfNotAuthorizedMiddleware.js index 85e67ad..548830e 100644 --- a/src/middlewares/rejectIfNotAuthorizedMiddleware.js +++ b/src/middlewares/rejectIfNotAuthorizedMiddleware.js @@ -1,13 +1,23 @@ -const sessionService = require('../services/sessionService'); - -async function rejectIfNotAuthorizedMiddleware(req, res, next) { - if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { - return res.status(403).json({ - success: false, - message: 'Your session is not authorized.', - }); +class RejectIfNotAuthorizedMiddleware { + constructor({ sessionService }) { + this.sessionService = sessionService; + } + + provide() { + return async (req, res, next) => { + if ( + !(await this.sessionService.isSessionAuthorized( + req.cookies.sessionUuid + )) + ) { + return res.status(403).json({ + success: false, + message: 'Your session is not authorized.', + }); + } + next(); + }; } - next(); } -module.exports = rejectIfNotAuthorizedMiddleware; +module.exports = RejectIfNotAuthorizedMiddleware; From 6ba8eed42736865ee292ca6dc2290017cb4c2756 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 00:35:48 +0100 Subject: [PATCH 139/239] another --- src/middlewares/index.js | 6 +++++- src/middlewares/redirectHomeIfAuthorized.js | 22 ++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 4a13843..c4fc3c7 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,6 +1,5 @@ const redirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); -const redirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); const sessionService = require('../services/sessionService'); const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); @@ -20,6 +19,11 @@ const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware( sessionService ).provide(); +const RedirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); +const redirectHomeIfAuthorized = new RedirectHomeIfAuthorized( + sessionService +).provide(); + module.exports = { redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, diff --git a/src/middlewares/redirectHomeIfAuthorized.js b/src/middlewares/redirectHomeIfAuthorized.js index 9619c68..4fcca17 100644 --- a/src/middlewares/redirectHomeIfAuthorized.js +++ b/src/middlewares/redirectHomeIfAuthorized.js @@ -1,10 +1,18 @@ -const sessionService = require('../services/sessionService'); - -async function redirectHomeIfAuthorized(req, res, next) { - if (await sessionService.isSessionAuthorized(req.cookies.sessionUuid)) { - return res.redirect('/home'); +class RedirectHomeIfAuthorized { + constructor(sessionService) { + this.sessionService = sessionService; + } + + provide() { + return async (req, res, next) => { + if ( + await this.sessionService.isSessionAuthorized(req.cookies.sessionUuid) + ) { + return res.redirect('/home'); + } + next(); + }; } - next(); } -module.exports = redirectHomeIfAuthorized; +module.exports = RedirectHomeIfAuthorized; From c4131c82aaaf26833422aa8f9c86686bbe4a730c Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 00:39:16 +0100 Subject: [PATCH 140/239] another --- src/middlewares/index.js | 16 ++++++++----- src/middlewares/redirectHomeIfAuthorized.js | 2 +- .../redirectIfNotAuthorizedMiddleware.js | 24 +++++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/middlewares/index.js b/src/middlewares/index.js index c4fc3c7..c546507 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,4 +1,3 @@ -const redirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); const sessionService = require('../services/sessionService'); @@ -15,13 +14,18 @@ const createSessionMiddleware = new CreateSessionMiddlewareProvider({ }).provide(); const RejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); -const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware( - sessionService -).provide(); +const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware({ + sessionService, +}).provide(); const RedirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); -const redirectHomeIfAuthorized = new RedirectHomeIfAuthorized( - sessionService +const redirectHomeIfAuthorized = new RedirectHomeIfAuthorized({ + sessionService, +}).provide(); + +const RedirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); +const redirectIfNotAuthorizedMiddleware = new RedirectIfNotAuthorizedMiddleware( + { sessionService } ).provide(); module.exports = { diff --git a/src/middlewares/redirectHomeIfAuthorized.js b/src/middlewares/redirectHomeIfAuthorized.js index 4fcca17..f3aedf2 100644 --- a/src/middlewares/redirectHomeIfAuthorized.js +++ b/src/middlewares/redirectHomeIfAuthorized.js @@ -1,5 +1,5 @@ class RedirectHomeIfAuthorized { - constructor(sessionService) { + constructor({ sessionService }) { this.sessionService = sessionService; } diff --git a/src/middlewares/redirectIfNotAuthorizedMiddleware.js b/src/middlewares/redirectIfNotAuthorizedMiddleware.js index f4b9e47..353f21c 100644 --- a/src/middlewares/redirectIfNotAuthorizedMiddleware.js +++ b/src/middlewares/redirectIfNotAuthorizedMiddleware.js @@ -1,10 +1,20 @@ -const sessionService = require('../services/sessionService'); - -async function redirectIfNotAuthorizedMiddleware(req, res, next) { - if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) { - return res.redirect('/login'); +class RedirectIfNotAuthorizedMiddleware { + constructor({ sessionService }) { + this.sessionService = sessionService; + } + + provide() { + return async (req, res, next) => { + if ( + !(await this.sessionService.isSessionAuthorized( + req.cookies.sessionUuid + )) + ) { + return res.redirect('/login'); + } + next(); + }; } - next(); } -module.exports = redirectIfNotAuthorizedMiddleware; +module.exports = RedirectIfNotAuthorizedMiddleware; From 3623ff11cb851394028665a8a202b45a9b08a0bd Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 00:46:51 +0100 Subject: [PATCH 141/239] last one --- src/middlewares/index.js | 9 ++++++-- ...directIfMissingProfileDetailsMiddleware.js | 23 +++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/middlewares/index.js b/src/middlewares/index.js index c546507..017f16d 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,5 +1,3 @@ -const redirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); - const sessionService = require('../services/sessionService'); const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); const attachPublicKeyMiddleware = new AttachPublicKeyMiddlewareProvider( @@ -28,6 +26,13 @@ const redirectIfNotAuthorizedMiddleware = new RedirectIfNotAuthorizedMiddleware( { sessionService } ).provide(); +const profileService = require('../services/profileService'); +const RedirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); +const redirectIfMissingProfileDetailsMiddleware = + new RedirectIfMissingProfileDetailsMiddleware({ + profileService, + }).provide(); + module.exports = { redirectIfNotAuthorizedMiddleware, attachPublicKeyMiddleware, diff --git a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js index 934048d..d7e758f 100644 --- a/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js +++ b/src/middlewares/redirectIfMissingProfileDetailsMiddleware.js @@ -1,12 +1,17 @@ -const profileService = require('../services/profileService'); - -async function redirectIfMissingProfileDetailsMiddleware(req, res, next) { - const publicKey = req.cookies.publicKey; - if (!(await profileService.areProfileDetailsComplete(publicKey))) { - res.redirect('/createProfile'); +class RedirectIfMissingProfileDetailsMiddleware { + constructor({ profileService }) { + this.profileService = profileService; } - next(); -} + provide() { + return async (req, res, next) => { + const publicKey = req.cookies.publicKey; + if (!(await this.profileService.areProfileDetailsComplete(publicKey))) { + res.redirect('/createProfile'); + } -module.exports = redirectIfMissingProfileDetailsMiddleware; + next(); + }; + } +} +module.exports = RedirectIfMissingProfileDetailsMiddleware; From 1dca924b837ae5da412dff8976d92ca7c244bf14 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 01:01:12 +0100 Subject: [PATCH 142/239] all middlewares provider --- src/dependencies.js | 12 ++- src/middlewares/attachPublicKeyMiddleware.js | 2 +- src/middlewares/index.js | 86 +++++++++++--------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index b182680..668261a 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -3,12 +3,18 @@ const express = require('express'); function buildDependencies() { const dependencies = {}; - const middlewares = require('./middlewares'); - dependencies.middlewares = middlewares; - + const constants = require('./constants'); const services = require('./services'); const errors = require('./errors'); + const MiddlewaresProvider = require('./middlewares'); + const middlewares = new MiddlewaresProvider({ + constants, + sessionService: services.sessionService, + profileService: services.profileService, + }).provide(); + dependencies.middlewares = middlewares; + const WebRoutesProvider = require('./routes/webRoutes'); const webRoutesProvider = new WebRoutesProvider({ express, diff --git a/src/middlewares/attachPublicKeyMiddleware.js b/src/middlewares/attachPublicKeyMiddleware.js index 4a60104..05c7758 100644 --- a/src/middlewares/attachPublicKeyMiddleware.js +++ b/src/middlewares/attachPublicKeyMiddleware.js @@ -1,5 +1,5 @@ class AttachPublicKeyMiddlewareProvider { - constructor(sessionService) { + constructor({ sessionService }) { this.sessionService = sessionService; } diff --git a/src/middlewares/index.js b/src/middlewares/index.js index 017f16d..1a316a1 100644 --- a/src/middlewares/index.js +++ b/src/middlewares/index.js @@ -1,43 +1,55 @@ -const sessionService = require('../services/sessionService'); -const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); -const attachPublicKeyMiddleware = new AttachPublicKeyMiddlewareProvider( - sessionService -).provide(); +class MiddlewaresProvider { + constructor({ constants, sessionService, profileService }) { + this.constants = constants; + this.sessionService = sessionService; + this.profileService = profileService; + } -const constants = require('../constants'); -const CreateSessionMiddlewareProvider = require('./createSessionMiddleware'); -const createSessionMiddleware = new CreateSessionMiddlewareProvider({ - constants, - sessionService, -}).provide(); + provide() { + const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware'); + const attachPublicKeyMiddleware = new AttachPublicKeyMiddlewareProvider({ + sessionService: this.sessionService, + }).provide(); -const RejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); -const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware({ - sessionService, -}).provide(); + const CreateSessionMiddlewareProvider = require('./createSessionMiddleware'); + const createSessionMiddleware = new CreateSessionMiddlewareProvider({ + constants: this.constants, + sessionService: this.sessionService, + }).provide(); -const RedirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); -const redirectHomeIfAuthorized = new RedirectHomeIfAuthorized({ - sessionService, -}).provide(); + const RejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware'); + const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware( + { + sessionService: this.sessionService, + } + ).provide(); -const RedirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); -const redirectIfNotAuthorizedMiddleware = new RedirectIfNotAuthorizedMiddleware( - { sessionService } -).provide(); + const RedirectHomeIfAuthorized = require('./redirectHomeIfAuthorized'); + const redirectHomeIfAuthorized = new RedirectHomeIfAuthorized({ + sessionService: this.sessionService, + }).provide(); -const profileService = require('../services/profileService'); -const RedirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); -const redirectIfMissingProfileDetailsMiddleware = - new RedirectIfMissingProfileDetailsMiddleware({ - profileService, - }).provide(); + const RedirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware'); + const redirectIfNotAuthorizedMiddleware = + new RedirectIfNotAuthorizedMiddleware({ + sessionService: this.sessionService, + }).provide(); -module.exports = { - redirectIfNotAuthorizedMiddleware, - attachPublicKeyMiddleware, - redirectIfMissingProfileDetailsMiddleware, - redirectHomeIfAuthorized, - rejectIfNotAuthorizedMiddleware, - createSessionMiddleware, -}; + const RedirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware'); + const redirectIfMissingProfileDetailsMiddleware = + new RedirectIfMissingProfileDetailsMiddleware({ + profileService: this.profileService, + }).provide(); + + return { + redirectIfNotAuthorizedMiddleware, + attachPublicKeyMiddleware, + redirectIfMissingProfileDetailsMiddleware, + redirectHomeIfAuthorized, + rejectIfNotAuthorizedMiddleware, + createSessionMiddleware, + }; + } +} + +module.exports = MiddlewaresProvider; From cdc344c528831feb7bea528a8029313c5d7415b3 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 01:09:45 +0100 Subject: [PATCH 143/239] services thingy --- src/dependencies.js | 7 ++++--- src/services/index.js | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index 668261a..5b3e4c7 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -2,10 +2,11 @@ const express = require('express'); function buildDependencies() { const dependencies = {}; - - const constants = require('./constants'); - const services = require('./services'); const errors = require('./errors'); + const constants = require('./constants'); + + const ServicesProvider = require('./services'); + const services = new ServicesProvider().provide(); const MiddlewaresProvider = require('./middlewares'); const middlewares = new MiddlewaresProvider({ diff --git a/src/services/index.js b/src/services/index.js index 3e35e60..06fe6e3 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,3 +1,18 @@ +class ServicesProvider { + constructor() {} + + provide() { + return { + invitesService, + nostrService, + loginService, + sessionService, + profileService, + offerService, + }; + } +} + const invitesService = require('../services/invitesService'); const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); @@ -5,11 +20,4 @@ const sessionService = require('../services/sessionService'); const profileService = require('../services/profileService'); const offerService = require('../services/offerService'); -module.exports = { - invitesService, - nostrService, - loginService, - sessionService, - profileService, - offerService, -}; +module.exports = ServicesProvider; From 56aa41675105af2ccbd29fdf6b2150f71d5be62b Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 01:26:36 +0100 Subject: [PATCH 144/239] first service --- src/services/index.js | 15 ++-- src/services/offerService.js | 154 ++++++++++++++++++----------------- 2 files changed, 89 insertions(+), 80 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index 06fe6e3..027cb45 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -2,6 +2,14 @@ class ServicesProvider { constructor() {} provide() { + const invitesService = require('../services/invitesService'); + const nostrService = require('../services/nostrService'); + const loginService = require('../services/loginService'); + const sessionService = require('../services/sessionService'); + const profileService = require('../services/profileService'); + const OfferServiceProvider = require('../services/offerService'); + const offerService = new OfferServiceProvider().provide(); + return { invitesService, nostrService, @@ -13,11 +21,4 @@ class ServicesProvider { } } -const invitesService = require('../services/invitesService'); -const nostrService = require('../services/nostrService'); -const loginService = require('../services/loginService'); -const sessionService = require('../services/sessionService'); -const profileService = require('../services/profileService'); -const offerService = require('../services/offerService'); - module.exports = ServicesProvider; diff --git a/src/services/offerService.js b/src/services/offerService.js index ab1f19e..25f7b6d 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -4,76 +4,18 @@ const models = require('../models'); const errors = require('../errors'); -async function createOffer(publicKey, offerDetails) { - const offerCreated = await models.OfferCreated.create({ - uuid: uuid.v7(), - public_key: publicKey, - created_at: new Date().toISOString(), - }); - - await models.OfferDetailsSet.create({ - uuid: uuid.v7(), - offer_uuid: offerCreated.uuid, - wants: offerDetails.wants, - premium: offerDetails.premium, - trade_amount_eur: offerDetails.trade_amount_eur, - location_details: offerDetails.location_details, - time_availability_details: offerDetails.time_availability_details, - show_offer_to_trusted: offerDetails.show_offer_to_trusted, - show_offer_to_trusted_trusted: offerDetails.show_offer_to_trusted_trusted, - show_offer_to_all_members: offerDetails.show_offer_to_all_members, - is_onchain_accepted: offerDetails.is_onchain_accepted, - is_lightning_accepted: offerDetails.is_lightning_accepted, - are_big_notes_accepted: offerDetails.are_big_notes_accepted, - created_at: new Date().toISOString(), - }); -} - -async function deleteOffer(offerUuid) { - const offerExists = Boolean( - await models.OfferCreated.findOne({ where: { uuid: offerUuid } }) - ); - const offerHasBeenDeleted = Boolean( - await models.OfferDeleted.findOne({ where: { offer_uuid: offerUuid } }) - ); - - if (!offerExists || offerHasBeenDeleted) { - throw new errors.NotFoundError(`Could not find the offer.`); - } - - return models.OfferDeleted.create({ - uuid: uuid.v7(), - offer_uuid: offerUuid, - created_at: new Date().toISOString(), - }); -} - -async function getOffersByPublicKey(publicKey) { - const offers = await models.OfferCreated.findAll({ - where: { - public_key: publicKey, - }, - }); - - console.log(offers); - - if (!offers) { - return []; - } - - const offersToReturn = []; - if (offers) { - for (const someOffer of offers) { - const offerDetails = await models.OfferDetailsSet.findOne({ - where: { - offer_uuid: someOffer.uuid, - }, - order: [['created_at', 'DESC']], +class OfferServiceProvider { + provide() { + async function createOffer(publicKey, offerDetails) { + const offerCreated = await models.OfferCreated.create({ + uuid: uuid.v7(), + public_key: publicKey, + created_at: new Date().toISOString(), }); - offersToReturn.push({ - uuid: someOffer.uuid, - public_key: someOffer.public_key, + await models.OfferDetailsSet.create({ + uuid: uuid.v7(), + offer_uuid: offerCreated.uuid, wants: offerDetails.wants, premium: offerDetails.premium, trade_amount_eur: offerDetails.trade_amount_eur, @@ -86,12 +28,78 @@ async function getOffersByPublicKey(publicKey) { is_onchain_accepted: offerDetails.is_onchain_accepted, is_lightning_accepted: offerDetails.is_lightning_accepted, are_big_notes_accepted: offerDetails.are_big_notes_accepted, - created_at: someOffer.created_at, - last_updated_at: offerDetails.created_at, + created_at: new Date().toISOString(), }); } - } - return offersToReturn; + async function deleteOffer(offerUuid) { + const offerExists = Boolean( + await models.OfferCreated.findOne({ where: { uuid: offerUuid } }) + ); + const offerHasBeenDeleted = Boolean( + await models.OfferDeleted.findOne({ where: { offer_uuid: offerUuid } }) + ); + + if (!offerExists || offerHasBeenDeleted) { + throw new errors.NotFoundError(`Could not find the offer.`); + } + + return models.OfferDeleted.create({ + uuid: uuid.v7(), + offer_uuid: offerUuid, + created_at: new Date().toISOString(), + }); + } + + async function getOffersByPublicKey(publicKey) { + const offers = await models.OfferCreated.findAll({ + where: { + public_key: publicKey, + }, + }); + + console.log(offers); + + if (!offers) { + return []; + } + + const offersToReturn = []; + if (offers) { + for (const someOffer of offers) { + const offerDetails = await models.OfferDetailsSet.findOne({ + where: { + offer_uuid: someOffer.uuid, + }, + order: [['created_at', 'DESC']], + }); + + offersToReturn.push({ + uuid: someOffer.uuid, + public_key: someOffer.public_key, + wants: offerDetails.wants, + premium: offerDetails.premium, + trade_amount_eur: offerDetails.trade_amount_eur, + location_details: offerDetails.location_details, + time_availability_details: offerDetails.time_availability_details, + show_offer_to_trusted: offerDetails.show_offer_to_trusted, + show_offer_to_trusted_trusted: + offerDetails.show_offer_to_trusted_trusted, + show_offer_to_all_members: offerDetails.show_offer_to_all_members, + is_onchain_accepted: offerDetails.is_onchain_accepted, + is_lightning_accepted: offerDetails.is_lightning_accepted, + are_big_notes_accepted: offerDetails.are_big_notes_accepted, + created_at: someOffer.created_at, + last_updated_at: offerDetails.created_at, + }); + } + } + + return offersToReturn; + } + + return { createOffer, getOffersByPublicKey, deleteOffer }; + } } -module.exports = { createOffer, getOffersByPublicKey, deleteOffer }; + +module.exports = OfferServiceProvider; From a2939a7f2ece8870c265aa82b81b48656708f0d7 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 01:28:47 +0100 Subject: [PATCH 145/239] profile service --- src/services/index.js | 3 +- src/services/profileService.js | 66 ++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index 027cb45..e38dc48 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -6,7 +6,8 @@ class ServicesProvider { const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); const sessionService = require('../services/sessionService'); - const profileService = require('../services/profileService'); + const ProfileServiceProvider = require('../services/profileService'); + const profileService = new ProfileServiceProvider().provide(); const OfferServiceProvider = require('../services/offerService'); const offerService = new OfferServiceProvider().provide(); diff --git a/src/services/profileService.js b/src/services/profileService.js index a3013b0..34bcd6f 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -2,35 +2,41 @@ const uuid = require('uuid'); const models = require('../models'); -async function setContactDetails(publicKey, encryptedContactDetails) { - return await models.ContactDetailsSet.create({ - uuid: uuid.v7(), - public_key: publicKey, - encrypted_contact_details: encryptedContactDetails, - created_at: new Date().toISOString(), - }); +class ProfileServiceProvider { + provide() { + async function setContactDetails(publicKey, encryptedContactDetails) { + return await models.ContactDetailsSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + encrypted_contact_details: encryptedContactDetails, + created_at: new Date().toISOString(), + }); + } + + async function setNym(publicKey, nym) { + return await models.NymSet.create({ + uuid: uuid.v7(), + public_key: publicKey, + nym: nym, + created_at: new Date().toISOString(), + }); + } + + async function areProfileDetailsComplete(publicKey) { + const isNymSet = await models.NymSet.findOne({ + where: { public_key: publicKey }, + }); + const areContactDetailsSet = await models.ContactDetailsSet.findOne({ + where: { + public_key: publicKey, + }, + }); + + return isNymSet && areContactDetailsSet; + } + + return { setContactDetails, setNym, areProfileDetailsComplete }; + } } -async function setNym(publicKey, nym) { - return await models.NymSet.create({ - uuid: uuid.v7(), - public_key: publicKey, - nym: nym, - created_at: new Date().toISOString(), - }); -} - -async function areProfileDetailsComplete(publicKey) { - const isNymSet = await models.NymSet.findOne({ - where: { public_key: publicKey }, - }); - const areContactDetailsSet = await models.ContactDetailsSet.findOne({ - where: { - public_key: publicKey, - }, - }); - - return isNymSet && areContactDetailsSet; -} - -module.exports = { setContactDetails, setNym, areProfileDetailsComplete }; +module.exports = ProfileServiceProvider; From 69290f4c7a7dc39d0af5f0d884143c59713b116e Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 01:46:17 +0100 Subject: [PATCH 146/239] session service --- src/services/index.js | 7 +- src/services/sessionService.js | 160 +++++++++++++++++---------------- 2 files changed, 91 insertions(+), 76 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index e38dc48..f4c61c3 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -5,7 +5,12 @@ class ServicesProvider { const invitesService = require('../services/invitesService'); const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); - const sessionService = require('../services/sessionService'); + + const SessionServiceProvider = require('../services/sessionService'); + const sessionService = new SessionServiceProvider({ + invitesService, + }).provide(); + const ProfileServiceProvider = require('../services/profileService'); const profileService = new ProfileServiceProvider().provide(); const OfferServiceProvider = require('../services/offerService'); diff --git a/src/services/sessionService.js b/src/services/sessionService.js index fdf20fc..d489d60 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -2,92 +2,102 @@ const uuid = require('uuid'); const models = require('../models'); -const invitesService = require('./invitesService'); const constants = require('../constants'); -async function createSession(sessionUuid) { - const currentTimestamp = new Date(); - const expiryTimestamp = new Date(currentTimestamp.getTime()); - expiryTimestamp.setSeconds( - expiryTimestamp.getSeconds() + constants.DEFAULT_SESSION_DURATION_SECONDS - ); - - return await models.SessionCreated.create({ - uuid: sessionUuid, - created_at: currentTimestamp.toISOString(), - expires_at: expiryTimestamp.toISOString(), - }); -} - -async function isSessionValid(sessionUuid) { - const currentSession = await models.SessionCreated.findOne({ - where: { - uuid: sessionUuid, - }, - }); - - if (!currentSession) { - return false; +class SessionServiceProvider { + constructor({ invitesService }) { + this.invitesService = invitesService; } - if (currentSession.expires_at <= new Date()) { - return false; - } + provide() { + async function createSession(sessionUuid) { + const currentTimestamp = new Date(); + const expiryTimestamp = new Date(currentTimestamp.getTime()); + expiryTimestamp.setSeconds( + expiryTimestamp.getSeconds() + + constants.DEFAULT_SESSION_DURATION_SECONDS + ); - return true; -} + return await models.SessionCreated.create({ + uuid: sessionUuid, + created_at: currentTimestamp.toISOString(), + expires_at: expiryTimestamp.toISOString(), + }); + } -async function relateSessionToPublicKey(sessionUuid, publicKey) { - if (!(await isSessionValid(sessionUuid))) { - throw Error('Session is not valid anymore.'); - } + async function isSessionValid(sessionUuid) { + const currentSession = await models.SessionCreated.findOne({ + where: { + uuid: sessionUuid, + }, + }); - if (!(await invitesService.isPublicKeySignedUp(publicKey))) { - throw Error('Public key is not signed up.'); - } + if (!currentSession) { + return false; + } - return models.SessionRelatedToPublickey.create({ - uuid: uuid.v7(), - session_uuid: sessionUuid, - public_key: publicKey, - created_at: new Date().toISOString(), - }); -} + if (currentSession.expires_at <= new Date()) { + return false; + } -async function isSessionAuthorized(sessionUuid) { - const isSessionRelatedToPublicKey = - await models.SessionRelatedToPublickey.findOne({ - where: { + return true; + } + + const relateSessionToPublicKey = async (sessionUuid, publicKey) => { + if (!(await isSessionValid(sessionUuid))) { + throw Error('Session is not valid anymore.'); + } + + if (!(await this.invitesService.isPublicKeySignedUp(publicKey))) { + throw Error('Public key is not signed up.'); + } + + return models.SessionRelatedToPublickey.create({ + uuid: uuid.v7(), session_uuid: sessionUuid, - }, - }); + public_key: publicKey, + created_at: new Date().toISOString(), + }); + }; - if (isSessionRelatedToPublicKey) { - return true; + async function isSessionAuthorized(sessionUuid) { + const isSessionRelatedToPublicKey = + await models.SessionRelatedToPublickey.findOne({ + where: { + session_uuid: sessionUuid, + }, + }); + + if (isSessionRelatedToPublicKey) { + return true; + } + + return false; + } + + async function getPublicKeyRelatedToSession(sessionUuid) { + const sessionRelatedToPublickey = + await models.SessionRelatedToPublickey.findOne({ + where: { + session_uuid: sessionUuid, + }, + }); + + if (sessionRelatedToPublickey) { + return sessionRelatedToPublickey.public_key; + } + + return null; + } + + return { + createSession, + isSessionValid, + relateSessionToPublicKey, + isSessionAuthorized, + getPublicKeyRelatedToSession, + }; } - - return false; } -async function getPublicKeyRelatedToSession(sessionUuid) { - const sessionRelatedToPublickey = - await models.SessionRelatedToPublickey.findOne({ - where: { - session_uuid: sessionUuid, - }, - }); - - if (sessionRelatedToPublickey) { - return sessionRelatedToPublickey.public_key; - } - - return null; -} - -module.exports = { - createSession, - isSessionValid, - relateSessionToPublicKey, - isSessionAuthorized, - getPublicKeyRelatedToSession, -}; +module.exports = SessionServiceProvider; From 15217dc77a9b367edcc88b6a87f8613eb9d9440b Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 01:59:23 +0100 Subject: [PATCH 147/239] invites --- src/commands/createAppInvite.js | 6 +- src/services/index.js | 6 +- src/services/invitesService.js | 226 +++++++++++++++++--------------- 3 files changed, 131 insertions(+), 107 deletions(-) diff --git a/src/commands/createAppInvite.js b/src/commands/createAppInvite.js index 0767123..f4407bb 100644 --- a/src/commands/createAppInvite.js +++ b/src/commands/createAppInvite.js @@ -1,4 +1,8 @@ -const invitesService = require('../services/invitesService'); +const nostrService = require('../services/nostrService'); +const InvitesServiceProvider = require('../services/invitesService'); +const invitesService = new InvitesServiceProvider({ + nostrService, +}).provide(); module.exports = async function createAppInvite(inviterNpub) { const appInvite = await invitesService.createAppInvite(inviterNpub); diff --git a/src/services/index.js b/src/services/index.js index f4c61c3..efb197d 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -2,10 +2,14 @@ class ServicesProvider { constructor() {} provide() { - const invitesService = require('../services/invitesService'); const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); + const InvitesServiceProvider = require('../services/invitesService'); + const invitesService = new InvitesServiceProvider({ + nostrService, + }).provide(); + const SessionServiceProvider = require('../services/sessionService'); const sessionService = new SessionServiceProvider({ invitesService, diff --git a/src/services/invitesService.js b/src/services/invitesService.js index 16d7805..75e96f1 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -4,117 +4,133 @@ const nostrService = require('./nostrService'); const models = require('../models'); const errors = require('../errors'); -async function appInviteExists(inviteUuid) { - const invite = await models.AppInviteCreated.findOne({ - where: { uuid: inviteUuid }, - }); - if (invite) { - return true; - } - return false; -} - -async function getAppInvite(inviteUuid) { - const invite = await models.AppInviteCreated.findOne({ - where: { uuid: inviteUuid }, - }); - return invite; -} - -async function isAppInviteSpent(appInviteUuid) { - const signUpChallengeCompleted = - await models.SignUpChallengeCompleted.findOne({ - where: { - app_invite_uuid: appInviteUuid, - }, - }); - - if (signUpChallengeCompleted) { - return true; - } - return false; -} - -async function createAppInvite(inviterPubKey) { - return await models.AppInviteCreated.create({ - uuid: uuid.v7(), - inviter_pub_key: inviterPubKey, - created_at: new Date().toISOString(), - }); -} - -async function createSignUpChallenge(appInviteUuid) { - if (!(await appInviteExists(appInviteUuid))) { - throw new errors.NotFoundError("Invite doesn't exist."); +class InvitesServiceProvider { + constructor({ nostrService }) { + this.nostrService = nostrService; } - if (await isAppInviteSpent(appInviteUuid)) { - throw new errors.AlreadyUsedError('Invite has already been used.'); - } - - const nostrChallenge = await nostrService.createNostrChallenge(); - - return await models.SignUpChallengeCreated.create({ - uuid: uuid.v7(), - nostr_challenge_uuid: nostrChallenge.uuid, - app_invite_uuid: appInviteUuid, - created_at: new Date().toISOString(), - }); -} - -async function verifySignUpChallenge(signedEvent) { - const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge'); - const challenge = challengeTag[1]; - - const nostrChallenge = await nostrService.getNostrChallenge(null, challenge); - - const signUpChallenge = await models.SignUpChallengeCreated.findOne({ - where: { - nostr_challenge_uuid: nostrChallenge.uuid, - }, - }); - - if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError('This challenge has already been used.'); - } - - const completedNostrChallenge = - await nostrService.verifyNostrChallenge(signedEvent); - - const completedSignUpChallenge = await models.SignUpChallengeCompleted.create( - { - uuid: uuid.v7(), - nostr_challenge_completed_uuid: completedNostrChallenge.uuid, - app_invite_uuid: signUpChallenge.app_invite_uuid, - public_key: completedNostrChallenge.public_key, - created_at: new Date().toISOString(), + provide() { + async function appInviteExists(inviteUuid) { + const invite = await models.AppInviteCreated.findOne({ + where: { uuid: inviteUuid }, + }); + if (invite) { + return true; + } + return false; } - ); - return completedSignUpChallenge; -} + async function getAppInvite(inviteUuid) { + const invite = await models.AppInviteCreated.findOne({ + where: { uuid: inviteUuid }, + }); + return invite; + } -async function isPublicKeySignedUp(publicKey) { - const signUpChallengeCompleted = - await models.SignUpChallengeCompleted.findOne({ - where: { - public_key: publicKey, - }, - }); + async function isAppInviteSpent(appInviteUuid) { + const signUpChallengeCompleted = + await models.SignUpChallengeCompleted.findOne({ + where: { + app_invite_uuid: appInviteUuid, + }, + }); - if (signUpChallengeCompleted) { - return true; + if (signUpChallengeCompleted) { + return true; + } + return false; + } + + async function createAppInvite(inviterPubKey) { + return await models.AppInviteCreated.create({ + uuid: uuid.v7(), + inviter_pub_key: inviterPubKey, + created_at: new Date().toISOString(), + }); + } + + async function createSignUpChallenge(appInviteUuid) { + if (!(await appInviteExists(appInviteUuid))) { + throw new errors.NotFoundError("Invite doesn't exist."); + } + + if (await isAppInviteSpent(appInviteUuid)) { + throw new errors.AlreadyUsedError('Invite has already been used.'); + } + + const nostrChallenge = await nostrService.createNostrChallenge(); + + return await models.SignUpChallengeCreated.create({ + uuid: uuid.v7(), + nostr_challenge_uuid: nostrChallenge.uuid, + app_invite_uuid: appInviteUuid, + created_at: new Date().toISOString(), + }); + } + + async function verifySignUpChallenge(signedEvent) { + const challengeTag = signedEvent.tags.find( + (tag) => tag[0] === 'challenge' + ); + const challenge = challengeTag[1]; + + const nostrChallenge = await nostrService.getNostrChallenge( + null, + challenge + ); + + const signUpChallenge = await models.SignUpChallengeCreated.findOne({ + where: { + nostr_challenge_uuid: nostrChallenge.uuid, + }, + }); + + if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { + throw new errors.AlreadyUsedError( + 'This challenge has already been used.' + ); + } + + const completedNostrChallenge = + await nostrService.verifyNostrChallenge(signedEvent); + + const completedSignUpChallenge = + await models.SignUpChallengeCompleted.create({ + uuid: uuid.v7(), + nostr_challenge_completed_uuid: completedNostrChallenge.uuid, + app_invite_uuid: signUpChallenge.app_invite_uuid, + public_key: completedNostrChallenge.public_key, + created_at: new Date().toISOString(), + }); + + return completedSignUpChallenge; + } + + async function isPublicKeySignedUp(publicKey) { + const signUpChallengeCompleted = + await models.SignUpChallengeCompleted.findOne({ + where: { + public_key: publicKey, + }, + }); + + if (signUpChallengeCompleted) { + return true; + } + + return false; + } + + return { + appInviteExists, + getAppInvite, + isAppInviteSpent, + createAppInvite, + createSignUpChallenge, + verifySignUpChallenge, + isPublicKeySignedUp, + }; } - - return false; } -module.exports = { - appInviteExists, - getAppInvite, - isAppInviteSpent, - createAppInvite, - createSignUpChallenge, - verifySignUpChallenge, - isPublicKeySignedUp, -}; +module.exports = InvitesServiceProvider; From f8a185e879b3ef4676a9ba119a9fa7dc4771c827 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 6 Mar 2025 02:19:23 +0100 Subject: [PATCH 148/239] nostr service and fix usage in invites --- src/commands/createAppInvite.js | 3 +- src/services/index.js | 3 +- src/services/invitesService.js | 17 ++- src/services/nostrService.js | 211 +++++++++++++++++--------------- 4 files changed, 124 insertions(+), 110 deletions(-) diff --git a/src/commands/createAppInvite.js b/src/commands/createAppInvite.js index f4407bb..6b36583 100644 --- a/src/commands/createAppInvite.js +++ b/src/commands/createAppInvite.js @@ -1,4 +1,5 @@ -const nostrService = require('../services/nostrService'); +const NostrServiceProvider = require('../services/nostrService'); +const nostrService = new NostrServiceProvider().provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ nostrService, diff --git a/src/services/index.js b/src/services/index.js index efb197d..deaaa8b 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -2,9 +2,10 @@ class ServicesProvider { constructor() {} provide() { - const nostrService = require('../services/nostrService'); const loginService = require('../services/loginService'); + const NostrServiceProvider = require('../services/nostrService'); + const nostrService = new NostrServiceProvider().provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ nostrService, diff --git a/src/services/invitesService.js b/src/services/invitesService.js index 75e96f1..6b3fde0 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -1,6 +1,5 @@ const uuid = require('uuid'); -const nostrService = require('./nostrService'); const models = require('../models'); const errors = require('../errors'); @@ -49,7 +48,7 @@ class InvitesServiceProvider { }); } - async function createSignUpChallenge(appInviteUuid) { + const createSignUpChallenge = async (appInviteUuid) => { if (!(await appInviteExists(appInviteUuid))) { throw new errors.NotFoundError("Invite doesn't exist."); } @@ -58,7 +57,7 @@ class InvitesServiceProvider { throw new errors.AlreadyUsedError('Invite has already been used.'); } - const nostrChallenge = await nostrService.createNostrChallenge(); + const nostrChallenge = await this.nostrService.createNostrChallenge(); return await models.SignUpChallengeCreated.create({ uuid: uuid.v7(), @@ -66,15 +65,15 @@ class InvitesServiceProvider { app_invite_uuid: appInviteUuid, created_at: new Date().toISOString(), }); - } + }; - async function verifySignUpChallenge(signedEvent) { + const verifySignUpChallenge = async (signedEvent) => { const challengeTag = signedEvent.tags.find( (tag) => tag[0] === 'challenge' ); const challenge = challengeTag[1]; - const nostrChallenge = await nostrService.getNostrChallenge( + const nostrChallenge = await this.nostrService.getNostrChallenge( null, challenge ); @@ -85,14 +84,14 @@ class InvitesServiceProvider { }, }); - if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { + if (await this.nostrService.hasNostrChallengeBeenCompleted(challenge)) { throw new errors.AlreadyUsedError( 'This challenge has already been used.' ); } const completedNostrChallenge = - await nostrService.verifyNostrChallenge(signedEvent); + await this.nostrService.verifyNostrChallenge(signedEvent); const completedSignUpChallenge = await models.SignUpChallengeCompleted.create({ @@ -104,7 +103,7 @@ class InvitesServiceProvider { }); return completedSignUpChallenge; - } + }; async function isPublicKeySignedUp(publicKey) { const signUpChallengeCompleted = diff --git a/src/services/nostrService.js b/src/services/nostrService.js index 412d0ba..51cb0d3 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -8,105 +8,118 @@ const models = require('../models'); const constants = require('../constants'); const errors = require('../errors'); -async function createNostrChallenge() { - const currentTimestamp = new Date(); - const expiryTimestamp = new Date(currentTimestamp.getTime()); - expiryTimestamp.setSeconds( - expiryTimestamp.getSeconds() + - constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS - ); +class NostrServiceProvider { + constructor() {} - const nostrChallenge = await models.NostrChallengeCreated.create({ - uuid: uuid.v7(), - challenge: crypto.randomBytes(32).toString('hex'), - expires_at: expiryTimestamp.toISOString(), - created_at: currentTimestamp.toISOString(), - }); + provide() { + async function createNostrChallenge() { + const currentTimestamp = new Date(); + const expiryTimestamp = new Date(currentTimestamp.getTime()); + expiryTimestamp.setSeconds( + expiryTimestamp.getSeconds() + + constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS + ); - return nostrChallenge; + const nostrChallenge = await models.NostrChallengeCreated.create({ + uuid: uuid.v7(), + challenge: crypto.randomBytes(32).toString('hex'), + expires_at: expiryTimestamp.toISOString(), + created_at: currentTimestamp.toISOString(), + }); + + return nostrChallenge; + } + + async function getNostrChallenge( + nostrChallengeUuid = null, + challenge = null + ) { + if (nostrChallengeUuid) { + return await models.NostrChallengeCreated.findOne({ + where: { + uuid: nostrChallengeUuid, + }, + }); + } + + if (challenge) { + return await models.NostrChallengeCreated.findOne({ + where: { + challenge, + }, + }); + } + + throw Error('You need to pass a uuid or a challenge.'); + } + + async function verifyNostrChallenge(signedEvent) { + const challengeTag = signedEvent.tags.find( + (tag) => tag[0] === 'challenge' + ); + const challenge = challengeTag[1]; + + if (!(await isNostrChallengeFresh(challenge))) { + throw TimeoutError('Challenge expired, request new one.'); + } + + if (await hasNostrChallengeBeenCompleted(challenge)) { + throw new errors.AlreadyUsedError( + 'Challenge already used, request new one.' + ); + } + + const isSignatureValid = verifyEvent(signedEvent); + if (!isSignatureValid) { + throw new errors.InvalidSignatureError('Signature is not valid.'); + } + + return await models.NostrChallengeCompleted.create({ + uuid: uuid.v7(), + challenge: challenge, + signed_event: signedEvent, + public_key: signedEvent.pubkey, + created_at: new Date().toISOString(), + }); + } + + async function isNostrChallengeFresh(challengeString) { + const nostrChallenge = await models.NostrChallengeCreated.findOne({ + where: { + challenge: challengeString, + expires_at: { + [Op.gt]: new Date(), + }, + }, + }); + + if (nostrChallenge) { + return true; + } + return false; + } + + async function hasNostrChallengeBeenCompleted(challengeString) { + const completedNostrChallenge = + await models.NostrChallengeCompleted.findOne({ + where: { + challenge: challengeString, + }, + }); + + if (completedNostrChallenge) { + return true; + } + return false; + } + + return { + createNostrChallenge, + getNostrChallenge, + verifyNostrChallenge, + isNostrChallengeFresh, + hasNostrChallengeBeenCompleted, + }; + } } - -async function getNostrChallenge(nostrChallengeUuid = null, challenge = null) { - if (nostrChallengeUuid) { - return await models.NostrChallengeCreated.findOne({ - where: { - uuid: nostrChallengeUuid, - }, - }); - } - - if (challenge) { - return await models.NostrChallengeCreated.findOne({ - where: { - challenge, - }, - }); - } - - throw Error('You need to pass a uuid or a challenge.'); -} - -async function verifyNostrChallenge(signedEvent) { - const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge'); - const challenge = challengeTag[1]; - - if (!(await isNostrChallengeFresh(challenge))) { - throw TimeoutError('Challenge expired, request new one.'); - } - - if (await hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError( - 'Challenge already used, request new one.' - ); - } - - const isSignatureValid = verifyEvent(signedEvent); - if (!isSignatureValid) { - throw new errors.InvalidSignatureError('Signature is not valid.'); - } - - return await models.NostrChallengeCompleted.create({ - uuid: uuid.v7(), - challenge: challenge, - signed_event: signedEvent, - public_key: signedEvent.pubkey, - created_at: new Date().toISOString(), - }); -} - -async function isNostrChallengeFresh(challengeString) { - const nostrChallenge = await models.NostrChallengeCreated.findOne({ - where: { - challenge: challengeString, - expires_at: { - [Op.gt]: new Date(), - }, - }, - }); - - if (nostrChallenge) { - return true; - } - return false; -} - -async function hasNostrChallengeBeenCompleted(challengeString) { - const completedNostrChallenge = await models.NostrChallengeCompleted.findOne({ - where: { - challenge: challengeString, - }, - }); - - if (completedNostrChallenge) { - return true; - } - return false; -} - -module.exports = { - createNostrChallenge, - getNostrChallenge, - verifyNostrChallenge, - isNostrChallengeFresh, - hasNostrChallengeBeenCompleted, -}; +module.exports = NostrServiceProvider; From b680ede093d4fb6aee7921a181f8a00d8a4adb14 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 12:31:07 +0100 Subject: [PATCH 149/239] finished all services --- src/services/index.js | 12 +++- src/services/loginService.js | 105 ++++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index deaaa8b..2c0d0ce 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -2,7 +2,11 @@ class ServicesProvider { constructor() {} provide() { - const loginService = require('../services/loginService'); + /* + Up next: + - start passing all service stuff from outside + - also remember to get CLi fixed and working + */ const NostrServiceProvider = require('../services/nostrService'); const nostrService = new NostrServiceProvider().provide(); @@ -11,6 +15,12 @@ class ServicesProvider { nostrService, }).provide(); + const LoginServiceProvider = require('../services/loginService'); + const loginService = new LoginServiceProvider({ + nostrService, + invitesService, + }).provide(); + const SessionServiceProvider = require('../services/sessionService'); const sessionService = new SessionServiceProvider({ invitesService, diff --git a/src/services/loginService.js b/src/services/loginService.js index 3de0090..db6b2cf 100644 --- a/src/services/loginService.js +++ b/src/services/loginService.js @@ -1,57 +1,70 @@ const uuid = require('uuid'); -const nostrService = require('./nostrService'); -const invitesService = require('./invitesService'); const models = require('../models'); const errors = require('../errors'); -async function createLoginChallenge() { - const nostrChallenge = await nostrService.createNostrChallenge(); - - return await models.LoginChallengeCreated.create({ - uuid: uuid.v7(), - nostr_challenge_uuid: nostrChallenge.uuid, - created_at: new Date().toISOString(), - }); -} - -async function verifyLoginChallenge(signedEvent) { - const challengeTag = signedEvent.tags.find((tag) => tag[0] === 'challenge'); - const challenge = challengeTag[1]; - - if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError('This challenge has already been used.'); +class LoginServiceProvider { + constructor({ nostrService, invitesService }) { + this.nostrService = nostrService; + this.invitesService = invitesService; } - const completedNostrChallenge = - await nostrService.verifyNostrChallenge(signedEvent); + provide() { + const createLoginChallenge = async () => { + const nostrChallenge = await this.nostrService.createNostrChallenge(); - if ( - !(await invitesService.isPublicKeySignedUp( - completedNostrChallenge.public_key - )) - ) { - console.log('helo4'); - throw new errors.ForbiddenError( - `Public key ${completedNostrChallenge.public_key} is not authorized.` - ); + return await models.LoginChallengeCreated.create({ + uuid: uuid.v7(), + nostr_challenge_uuid: nostrChallenge.uuid, + created_at: new Date().toISOString(), + }); + }; + + const verifyLoginChallenge = async (signedEvent) => { + const challengeTag = signedEvent.tags.find( + (tag) => tag[0] === 'challenge' + ); + const challenge = challengeTag[1]; + + if (await this.nostrService.hasNostrChallengeBeenCompleted(challenge)) { + throw new errors.AlreadyUsedError( + 'This challenge has already been used.' + ); + } + + const completedNostrChallenge = + await this.nostrService.verifyNostrChallenge(signedEvent); + + if ( + !(await this.invitesService.isPublicKeySignedUp( + completedNostrChallenge.public_key + )) + ) { + console.log('helo4'); + throw new errors.ForbiddenError( + `Public key ${completedNostrChallenge.public_key} is not authorized.` + ); + } + + const completedLoginChallenge = + await models.LoginChallengeCompleted.create({ + uuid: uuid.v7(), + nostr_challenge_completed_uuid: completedNostrChallenge.uuid, + public_key: completedNostrChallenge.public_key, + created_at: new Date().toISOString(), + }); + + console.log('helo3'); + console.log(completedLoginChallenge); + + return completedLoginChallenge; + }; + + return { + createLoginChallenge, + verifyLoginChallenge, + }; } - - const completedLoginChallenge = await models.LoginChallengeCompleted.create({ - uuid: uuid.v7(), - nostr_challenge_completed_uuid: completedNostrChallenge.uuid, - public_key: completedNostrChallenge.public_key, - created_at: new Date().toISOString(), - }); - - console.log('helo3'); - console.log(completedLoginChallenge); - - return completedLoginChallenge; } - -module.exports = { - createLoginChallenge, - verifyLoginChallenge, -}; +module.exports = LoginServiceProvider; From 9246da2e84355cad34415e59a77c53a5ae5ed6ea Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 12:52:15 +0100 Subject: [PATCH 150/239] cli now uses DI --- src/cli.js | 35 +++++++++++++++++++++++++-------- src/commands/createAppInvite.js | 23 +++++++++++++++++----- src/dependencies.js | 1 + 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/cli.js b/src/cli.js index 3776ee3..f4e36c8 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,13 +1,32 @@ const { Command } = require('commander'); -const program = new Command(); +const { buildDependencies } = require('./dependencies'); -const createAppInviteCommand = require('./commands/createAppInvite'); +function buildCLIDependencies() { + const appDependencies = buildDependencies(); -program.version('1.0.0').description('CLI for managing web app tasks'); + const CreateAppInviteProvider = require('./commands/createAppInvite'); + const createAppInvite = new CreateAppInviteProvider({ + nostrService: appDependencies.services.nostrService, + invitesService: appDependencies.services.invitesService, + }).provide(); -program - .command('createAppInvite ') - .description('Create an invite') - .action(createAppInviteCommand); + return { createAppInviteCommand: createAppInvite }; +} -program.parse(process.argv); +function buildCLI({ createAppInviteCommand }) { + const wipCli = new Command(); + wipCli.version('1.0.0').description('CLI for managing web app tasks'); + + wipCli + .command('createAppInvite ') + .description('Create an invite') + .action(createAppInviteCommand); + + return wipCli; +} + +const cliDependencies = buildCLIDependencies(); + +const cli = buildCLI(cliDependencies); + +cli.parse(process.argv); diff --git a/src/commands/createAppInvite.js b/src/commands/createAppInvite.js index 6b36583..1a7c41b 100644 --- a/src/commands/createAppInvite.js +++ b/src/commands/createAppInvite.js @@ -5,8 +5,21 @@ const invitesService = new InvitesServiceProvider({ nostrService, }).provide(); -module.exports = async function createAppInvite(inviterNpub) { - const appInvite = await invitesService.createAppInvite(inviterNpub); - console.log('Invite created'); - console.log(`Check at http://localhost/invite/${appInvite.uuid}`); -}; +class CreateAppInviteProvider { + constructor({ nostrService, invitesService }) { + this.nostrService = nostrService; + this.invitesService = invitesService; + } + + provide() { + const createAppInvite = async (inviterNpub) => { + const appInvite = await invitesService.createAppInvite(inviterNpub); + console.log('Invite created'); + console.log(`Check at http://localhost/invite/${appInvite.uuid}`); + }; + + return createAppInvite; + } +} + +module.exports = CreateAppInviteProvider; diff --git a/src/dependencies.js b/src/dependencies.js index 5b3e4c7..ce71d18 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -7,6 +7,7 @@ function buildDependencies() { const ServicesProvider = require('./services'); const services = new ServicesProvider().provide(); + dependencies.services = services; const MiddlewaresProvider = require('./middlewares'); const middlewares = new MiddlewaresProvider({ From 5c294b89a16d9a2e950d4d75503248c1ff2abd58 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 12:53:37 +0100 Subject: [PATCH 151/239] just a silly error change --- src/services/nostrService.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/nostrService.js b/src/services/nostrService.js index 51cb0d3..46573be 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -1,6 +1,6 @@ const uuid = require('uuid'); const crypto = require('crypto'); -const { Op, TimeoutError } = require('sequelize'); +const { Op } = require('sequelize'); const { verifyEvent } = require('nostr-tools'); const models = require('../models'); @@ -60,7 +60,7 @@ class NostrServiceProvider { const challenge = challengeTag[1]; if (!(await isNostrChallengeFresh(challenge))) { - throw TimeoutError('Challenge expired, request new one.'); + throw errors.ExpiredError('Challenge expired, request new one.'); } if (await hasNostrChallengeBeenCompleted(challenge)) { From 9f4bc729e24dd32a0377ed3fcf5d21add44b6734 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 13:03:44 +0100 Subject: [PATCH 152/239] finish the cli stuff --- src/cli.js | 1 - src/commands/createAppInvite.js | 12 ++---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/cli.js b/src/cli.js index f4e36c8..dae7913 100644 --- a/src/cli.js +++ b/src/cli.js @@ -6,7 +6,6 @@ function buildCLIDependencies() { const CreateAppInviteProvider = require('./commands/createAppInvite'); const createAppInvite = new CreateAppInviteProvider({ - nostrService: appDependencies.services.nostrService, invitesService: appDependencies.services.invitesService, }).provide(); diff --git a/src/commands/createAppInvite.js b/src/commands/createAppInvite.js index 1a7c41b..610c098 100644 --- a/src/commands/createAppInvite.js +++ b/src/commands/createAppInvite.js @@ -1,19 +1,11 @@ -const NostrServiceProvider = require('../services/nostrService'); -const nostrService = new NostrServiceProvider().provide(); -const InvitesServiceProvider = require('../services/invitesService'); -const invitesService = new InvitesServiceProvider({ - nostrService, -}).provide(); - class CreateAppInviteProvider { - constructor({ nostrService, invitesService }) { - this.nostrService = nostrService; + constructor({ invitesService }) { this.invitesService = invitesService; } provide() { const createAppInvite = async (inviterNpub) => { - const appInvite = await invitesService.createAppInvite(inviterNpub); + const appInvite = await this.invitesService.createAppInvite(inviterNpub); console.log('Invite created'); console.log(`Check at http://localhost/invite/${appInvite.uuid}`); }; From 73a71d3ccbfce734a35ac668d0e8c052b290e7dd Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 13:17:55 +0100 Subject: [PATCH 153/239] pass constants to nostr service --- src/services/index.js | 6 +++++- src/services/nostrService.js | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index 2c0d0ce..454dc5a 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,3 +1,5 @@ +const constants = require('../constants'); + class ServicesProvider { constructor() {} @@ -9,7 +11,9 @@ class ServicesProvider { */ const NostrServiceProvider = require('../services/nostrService'); - const nostrService = new NostrServiceProvider().provide(); + const nostrService = new NostrServiceProvider({ + constants: constants, + }).provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ nostrService, diff --git a/src/services/nostrService.js b/src/services/nostrService.js index 46573be..5239273 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -5,19 +5,20 @@ const { verifyEvent } = require('nostr-tools'); const models = require('../models'); -const constants = require('../constants'); const errors = require('../errors'); class NostrServiceProvider { - constructor() {} + constructor({ constants }) { + this.constants = constants; + } provide() { - async function createNostrChallenge() { + const createNostrChallenge = async () => { const currentTimestamp = new Date(); const expiryTimestamp = new Date(currentTimestamp.getTime()); expiryTimestamp.setSeconds( expiryTimestamp.getSeconds() + - constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS + this.constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS ); const nostrChallenge = await models.NostrChallengeCreated.create({ @@ -28,7 +29,7 @@ class NostrServiceProvider { }); return nostrChallenge; - } + }; async function getNostrChallenge( nostrChallengeUuid = null, From 00670f051fbfe72fd87e8c9bb152b5a4fdf6c4fc Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 13:27:28 +0100 Subject: [PATCH 154/239] no more models --- src/services/nostrService.js | 44 +++++++++++++++++------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/services/nostrService.js b/src/services/nostrService.js index 5239273..f881c5c 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -3,13 +3,11 @@ const crypto = require('crypto'); const { Op } = require('sequelize'); const { verifyEvent } = require('nostr-tools'); -const models = require('../models'); - -const errors = require('../errors'); - class NostrServiceProvider { - constructor({ constants }) { + constructor({ models, constants, errors }) { + this.models = models; this.constants = constants; + this.errors = errors; } provide() { @@ -21,7 +19,7 @@ class NostrServiceProvider { this.constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS ); - const nostrChallenge = await models.NostrChallengeCreated.create({ + const nostrChallenge = await this.models.NostrChallengeCreated.create({ uuid: uuid.v7(), challenge: crypto.randomBytes(32).toString('hex'), expires_at: expiryTimestamp.toISOString(), @@ -31,12 +29,12 @@ class NostrServiceProvider { return nostrChallenge; }; - async function getNostrChallenge( + const getNostrChallenge = async ( nostrChallengeUuid = null, challenge = null - ) { + ) => { if (nostrChallengeUuid) { - return await models.NostrChallengeCreated.findOne({ + return await this.models.NostrChallengeCreated.findOne({ where: { uuid: nostrChallengeUuid, }, @@ -44,7 +42,7 @@ class NostrServiceProvider { } if (challenge) { - return await models.NostrChallengeCreated.findOne({ + return await this.models.NostrChallengeCreated.findOne({ where: { challenge, }, @@ -52,40 +50,40 @@ class NostrServiceProvider { } throw Error('You need to pass a uuid or a challenge.'); - } + }; - async function verifyNostrChallenge(signedEvent) { + const verifyNostrChallenge = async (signedEvent) => { const challengeTag = signedEvent.tags.find( (tag) => tag[0] === 'challenge' ); const challenge = challengeTag[1]; if (!(await isNostrChallengeFresh(challenge))) { - throw errors.ExpiredError('Challenge expired, request new one.'); + throw this.errors.ExpiredError('Challenge expired, request new one.'); } if (await hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError( + throw new this.errors.AlreadyUsedError( 'Challenge already used, request new one.' ); } const isSignatureValid = verifyEvent(signedEvent); if (!isSignatureValid) { - throw new errors.InvalidSignatureError('Signature is not valid.'); + throw new this.errors.InvalidSignatureError('Signature is not valid.'); } - return await models.NostrChallengeCompleted.create({ + return await this.models.NostrChallengeCompleted.create({ uuid: uuid.v7(), challenge: challenge, signed_event: signedEvent, public_key: signedEvent.pubkey, created_at: new Date().toISOString(), }); - } + }; - async function isNostrChallengeFresh(challengeString) { - const nostrChallenge = await models.NostrChallengeCreated.findOne({ + const isNostrChallengeFresh = async (challengeString) => { + const nostrChallenge = await this.models.NostrChallengeCreated.findOne({ where: { challenge: challengeString, expires_at: { @@ -98,11 +96,11 @@ class NostrServiceProvider { return true; } return false; - } + }; - async function hasNostrChallengeBeenCompleted(challengeString) { + const hasNostrChallengeBeenCompleted = async (challengeString) => { const completedNostrChallenge = - await models.NostrChallengeCompleted.findOne({ + await this.models.NostrChallengeCompleted.findOne({ where: { challenge: challengeString, }, @@ -112,7 +110,7 @@ class NostrServiceProvider { return true; } return false; - } + }; return { createNostrChallenge, From 0da67c6104255abdd4b534fa5592b69233e77b39 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 13:28:00 +0100 Subject: [PATCH 155/239] missing errors import --- src/services/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/index.js b/src/services/index.js index 454dc5a..321fa54 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,4 +1,6 @@ +const models = require('../models'); const constants = require('../constants'); +const errors = require('../errors'); class ServicesProvider { constructor() {} @@ -12,7 +14,9 @@ class ServicesProvider { const NostrServiceProvider = require('../services/nostrService'); const nostrService = new NostrServiceProvider({ + models: models, constants: constants, + errors: errors, }).provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ From 544a134eb3a6d654c0c4affe9ca9c3c76c099f17 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:05:43 +0100 Subject: [PATCH 156/239] invite service --- src/services/index.js | 6 ++++- src/services/invitesService.js | 49 +++++++++++++++++----------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index 321fa54..04d47a3 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -12,15 +12,19 @@ class ServicesProvider { - also remember to get CLi fixed and working */ + //done const NostrServiceProvider = require('../services/nostrService'); const nostrService = new NostrServiceProvider({ models: models, constants: constants, errors: errors, }).provide(); + const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ - nostrService, + models: models, + errors: errors, + nostrService: nostrService, }).provide(); const LoginServiceProvider = require('../services/loginService'); diff --git a/src/services/invitesService.js b/src/services/invitesService.js index 6b3fde0..df357e3 100644 --- a/src/services/invitesService.js +++ b/src/services/invitesService.js @@ -1,34 +1,33 @@ const uuid = require('uuid'); -const models = require('../models'); -const errors = require('../errors'); - class InvitesServiceProvider { - constructor({ nostrService }) { + constructor({ models, errors, nostrService }) { + this.models = models; + this.errors = errors; this.nostrService = nostrService; } provide() { - async function appInviteExists(inviteUuid) { - const invite = await models.AppInviteCreated.findOne({ + const appInviteExists = async (inviteUuid) => { + const invite = await this.models.AppInviteCreated.findOne({ where: { uuid: inviteUuid }, }); if (invite) { return true; } return false; - } + }; - async function getAppInvite(inviteUuid) { - const invite = await models.AppInviteCreated.findOne({ + const getAppInvite = async (inviteUuid) => { + const invite = await this.models.AppInviteCreated.findOne({ where: { uuid: inviteUuid }, }); return invite; - } + }; - async function isAppInviteSpent(appInviteUuid) { + const isAppInviteSpent = async (appInviteUuid) => { const signUpChallengeCompleted = - await models.SignUpChallengeCompleted.findOne({ + await this.models.SignUpChallengeCompleted.findOne({ where: { app_invite_uuid: appInviteUuid, }, @@ -38,28 +37,28 @@ class InvitesServiceProvider { return true; } return false; - } + }; - async function createAppInvite(inviterPubKey) { - return await models.AppInviteCreated.create({ + const createAppInvite = async (inviterPubKey) => { + return await this.models.AppInviteCreated.create({ uuid: uuid.v7(), inviter_pub_key: inviterPubKey, created_at: new Date().toISOString(), }); - } + }; const createSignUpChallenge = async (appInviteUuid) => { if (!(await appInviteExists(appInviteUuid))) { - throw new errors.NotFoundError("Invite doesn't exist."); + throw new this.errors.NotFoundError("Invite doesn't exist."); } if (await isAppInviteSpent(appInviteUuid)) { - throw new errors.AlreadyUsedError('Invite has already been used.'); + throw new this.errors.AlreadyUsedError('Invite has already been used.'); } const nostrChallenge = await this.nostrService.createNostrChallenge(); - return await models.SignUpChallengeCreated.create({ + return await this.models.SignUpChallengeCreated.create({ uuid: uuid.v7(), nostr_challenge_uuid: nostrChallenge.uuid, app_invite_uuid: appInviteUuid, @@ -78,14 +77,14 @@ class InvitesServiceProvider { challenge ); - const signUpChallenge = await models.SignUpChallengeCreated.findOne({ + const signUpChallenge = await this.models.SignUpChallengeCreated.findOne({ where: { nostr_challenge_uuid: nostrChallenge.uuid, }, }); if (await this.nostrService.hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError( + throw new this.errors.AlreadyUsedError( 'This challenge has already been used.' ); } @@ -94,7 +93,7 @@ class InvitesServiceProvider { await this.nostrService.verifyNostrChallenge(signedEvent); const completedSignUpChallenge = - await models.SignUpChallengeCompleted.create({ + await this.models.SignUpChallengeCompleted.create({ uuid: uuid.v7(), nostr_challenge_completed_uuid: completedNostrChallenge.uuid, app_invite_uuid: signUpChallenge.app_invite_uuid, @@ -105,9 +104,9 @@ class InvitesServiceProvider { return completedSignUpChallenge; }; - async function isPublicKeySignedUp(publicKey) { + const isPublicKeySignedUp = async (publicKey) => { const signUpChallengeCompleted = - await models.SignUpChallengeCompleted.findOne({ + await this.models.SignUpChallengeCompleted.findOne({ where: { public_key: publicKey, }, @@ -118,7 +117,7 @@ class InvitesServiceProvider { } return false; - } + }; return { appInviteExists, From ba9b4d5ef300fdaa503001564fdf39930e50bc74 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:11:34 +0100 Subject: [PATCH 157/239] login service --- src/routes/apiRoutes.js | 2 -- src/services/index.js | 2 ++ src/services/loginService.js | 16 +++++++--------- src/services/nostrService.js | 4 +++- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 9ff1513..f97ae1a 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -129,8 +129,6 @@ class ApiRoutesProvider { completedLoginChallenge = await this.services.loginService.verifyLoginChallenge(signedEvent); } catch (error) { - console.log('helo5'); - console.log(error); if (error instanceof this.errors.ExpiredError) { return res.status(410).json({ success: false, diff --git a/src/services/index.js b/src/services/index.js index 04d47a3..eea745b 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -29,6 +29,8 @@ class ServicesProvider { const LoginServiceProvider = require('../services/loginService'); const loginService = new LoginServiceProvider({ + models, + errors, nostrService, invitesService, }).provide(); diff --git a/src/services/loginService.js b/src/services/loginService.js index db6b2cf..b4e74cd 100644 --- a/src/services/loginService.js +++ b/src/services/loginService.js @@ -1,11 +1,9 @@ const uuid = require('uuid'); -const models = require('../models'); - -const errors = require('../errors'); - class LoginServiceProvider { - constructor({ nostrService, invitesService }) { + constructor({ models, errors, nostrService, invitesService }) { + this.models = models; + this.errors = errors; this.nostrService = nostrService; this.invitesService = invitesService; } @@ -14,7 +12,7 @@ class LoginServiceProvider { const createLoginChallenge = async () => { const nostrChallenge = await this.nostrService.createNostrChallenge(); - return await models.LoginChallengeCreated.create({ + return await this.models.LoginChallengeCreated.create({ uuid: uuid.v7(), nostr_challenge_uuid: nostrChallenge.uuid, created_at: new Date().toISOString(), @@ -28,7 +26,7 @@ class LoginServiceProvider { const challenge = challengeTag[1]; if (await this.nostrService.hasNostrChallengeBeenCompleted(challenge)) { - throw new errors.AlreadyUsedError( + throw new this.errors.AlreadyUsedError( 'This challenge has already been used.' ); } @@ -42,13 +40,13 @@ class LoginServiceProvider { )) ) { console.log('helo4'); - throw new errors.ForbiddenError( + throw new this.errors.ForbiddenError( `Public key ${completedNostrChallenge.public_key} is not authorized.` ); } const completedLoginChallenge = - await models.LoginChallengeCompleted.create({ + await this.models.LoginChallengeCompleted.create({ uuid: uuid.v7(), nostr_challenge_completed_uuid: completedNostrChallenge.uuid, public_key: completedNostrChallenge.public_key, diff --git a/src/services/nostrService.js b/src/services/nostrService.js index f881c5c..7d87ea6 100644 --- a/src/services/nostrService.js +++ b/src/services/nostrService.js @@ -59,7 +59,9 @@ class NostrServiceProvider { const challenge = challengeTag[1]; if (!(await isNostrChallengeFresh(challenge))) { - throw this.errors.ExpiredError('Challenge expired, request new one.'); + throw new this.errors.ExpiredError( + 'Challenge expired, request new one.' + ); } if (await hasNostrChallengeBeenCompleted(challenge)) { From 0f4ccf98473180fa062c566cfab5651d5359cbac Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:29:58 +0100 Subject: [PATCH 158/239] wip sessionService --- src/services/sessionService.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/sessionService.js b/src/services/sessionService.js index d489d60..e8e563a 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -5,12 +5,14 @@ const models = require('../models'); const constants = require('../constants'); class SessionServiceProvider { - constructor({ invitesService }) { + constructor({ models, constants, invitesService }) { + this.models = models; + this.constants = constants; this.invitesService = invitesService; } provide() { - async function createSession(sessionUuid) { + const createSession = async (sessionUuid) => { const currentTimestamp = new Date(); const expiryTimestamp = new Date(currentTimestamp.getTime()); expiryTimestamp.setSeconds( @@ -18,12 +20,12 @@ class SessionServiceProvider { constants.DEFAULT_SESSION_DURATION_SECONDS ); - return await models.SessionCreated.create({ + return await this.models.SessionCreated.create({ uuid: sessionUuid, created_at: currentTimestamp.toISOString(), expires_at: expiryTimestamp.toISOString(), }); - } + }; async function isSessionValid(sessionUuid) { const currentSession = await models.SessionCreated.findOne({ From 49c3e970cb2215397a1555c482900692a93551dc Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:30:30 +0100 Subject: [PATCH 159/239] wip sessionService --- src/services/sessionService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/sessionService.js b/src/services/sessionService.js index e8e563a..2438d05 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -27,8 +27,8 @@ class SessionServiceProvider { }); }; - async function isSessionValid(sessionUuid) { - const currentSession = await models.SessionCreated.findOne({ + const isSessionValid = async (sessionUuid) => { + const currentSession = await this.models.SessionCreated.findOne({ where: { uuid: sessionUuid, }, @@ -43,7 +43,7 @@ class SessionServiceProvider { } return true; - } + }; const relateSessionToPublicKey = async (sessionUuid, publicKey) => { if (!(await isSessionValid(sessionUuid))) { From e85fae2f996c93004fa975a45a9ec5ffc0fd41d6 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:31:04 +0100 Subject: [PATCH 160/239] wip sessionService --- src/services/sessionService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/sessionService.js b/src/services/sessionService.js index 2438d05..96341ec 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -54,7 +54,7 @@ class SessionServiceProvider { throw Error('Public key is not signed up.'); } - return models.SessionRelatedToPublickey.create({ + return this.models.SessionRelatedToPublickey.create({ uuid: uuid.v7(), session_uuid: sessionUuid, public_key: publicKey, From 097bed525e64e92a770254f604604f0a75e61952 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:31:46 +0100 Subject: [PATCH 161/239] wip sessionService --- src/services/sessionService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/sessionService.js b/src/services/sessionService.js index 96341ec..ea0cc18 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -62,9 +62,9 @@ class SessionServiceProvider { }); }; - async function isSessionAuthorized(sessionUuid) { + const isSessionAuthorized = async (sessionUuid) => { const isSessionRelatedToPublicKey = - await models.SessionRelatedToPublickey.findOne({ + await this.models.SessionRelatedToPublickey.findOne({ where: { session_uuid: sessionUuid, }, @@ -75,7 +75,7 @@ class SessionServiceProvider { } return false; - } + }; async function getPublicKeyRelatedToSession(sessionUuid) { const sessionRelatedToPublickey = From 53219924ff35adb110d4e2d3d2984ce050246299 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:35:22 +0100 Subject: [PATCH 162/239] wip sessionService --- src/services/sessionService.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/services/sessionService.js b/src/services/sessionService.js index ea0cc18..a032f1b 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -1,7 +1,5 @@ const uuid = require('uuid'); -const models = require('../models'); - const constants = require('../constants'); class SessionServiceProvider { @@ -77,9 +75,9 @@ class SessionServiceProvider { return false; }; - async function getPublicKeyRelatedToSession(sessionUuid) { + const getPublicKeyRelatedToSession = async (sessionUuid) => { const sessionRelatedToPublickey = - await models.SessionRelatedToPublickey.findOne({ + await this.models.SessionRelatedToPublickey.findOne({ where: { session_uuid: sessionUuid, }, @@ -90,7 +88,7 @@ class SessionServiceProvider { } return null; - } + }; return { createSession, From a2e44553d61fa2b3d7aa54d7f7cfbeebcc159aae Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:35:42 +0100 Subject: [PATCH 163/239] wip sessionService --- src/services/sessionService.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/sessionService.js b/src/services/sessionService.js index a032f1b..e731846 100644 --- a/src/services/sessionService.js +++ b/src/services/sessionService.js @@ -1,7 +1,5 @@ const uuid = require('uuid'); -const constants = require('../constants'); - class SessionServiceProvider { constructor({ models, constants, invitesService }) { this.models = models; @@ -15,7 +13,7 @@ class SessionServiceProvider { const expiryTimestamp = new Date(currentTimestamp.getTime()); expiryTimestamp.setSeconds( expiryTimestamp.getSeconds() + - constants.DEFAULT_SESSION_DURATION_SECONDS + this.constants.DEFAULT_SESSION_DURATION_SECONDS ); return await this.models.SessionCreated.create({ From 42fea5523368194f1566a90d1835a33ba85845f3 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 15:35:55 +0100 Subject: [PATCH 164/239] finished session service --- src/services/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/index.js b/src/services/index.js index eea745b..bf41899 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -37,6 +37,8 @@ class ServicesProvider { const SessionServiceProvider = require('../services/sessionService'); const sessionService = new SessionServiceProvider({ + models, + constants, invitesService, }).provide(); From ec725f8e4ea1297279f2d542099c3bde88ae82d1 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 16:03:10 +0100 Subject: [PATCH 165/239] profile service --- src/routes/apiRoutes.js | 4 +--- src/services/index.js | 2 +- src/services/loginService.js | 4 ---- src/services/profileService.js | 24 ++++++++++++++---------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index f97ae1a..b93b1f0 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -148,7 +148,6 @@ class ApiRoutesProvider { }); } if (error instanceof this.errors.ForbiddenError) { - console.log('helo?1'); return res.status(403).json({ success: false, message: 'This public key is not authorized.', @@ -160,8 +159,7 @@ class ApiRoutesProvider { message: 'Unexpected error.', }); } - console.log('helo?2'); - console.log(completedLoginChallenge); + await this.services.sessionService.relateSessionToPublicKey( sessionUuid, completedLoginChallenge.public_key diff --git a/src/services/index.js b/src/services/index.js index bf41899..f71ab58 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -43,7 +43,7 @@ class ServicesProvider { }).provide(); const ProfileServiceProvider = require('../services/profileService'); - const profileService = new ProfileServiceProvider().provide(); + const profileService = new ProfileServiceProvider({ models }).provide(); const OfferServiceProvider = require('../services/offerService'); const offerService = new OfferServiceProvider().provide(); diff --git a/src/services/loginService.js b/src/services/loginService.js index b4e74cd..73a0b58 100644 --- a/src/services/loginService.js +++ b/src/services/loginService.js @@ -39,7 +39,6 @@ class LoginServiceProvider { completedNostrChallenge.public_key )) ) { - console.log('helo4'); throw new this.errors.ForbiddenError( `Public key ${completedNostrChallenge.public_key} is not authorized.` ); @@ -53,9 +52,6 @@ class LoginServiceProvider { created_at: new Date().toISOString(), }); - console.log('helo3'); - console.log(completedLoginChallenge); - return completedLoginChallenge; }; diff --git a/src/services/profileService.js b/src/services/profileService.js index 34bcd6f..391d953 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -3,37 +3,41 @@ const uuid = require('uuid'); const models = require('../models'); class ProfileServiceProvider { + constructor({ models }) { + this.models = models; + } provide() { - async function setContactDetails(publicKey, encryptedContactDetails) { - return await models.ContactDetailsSet.create({ + const setContactDetails = async (publicKey, encryptedContactDetails) => { + return await this.models.ContactDetailsSet.create({ uuid: uuid.v7(), public_key: publicKey, encrypted_contact_details: encryptedContactDetails, created_at: new Date().toISOString(), }); - } + }; - async function setNym(publicKey, nym) { - return await models.NymSet.create({ + const setNym = async (publicKey, nym) => { + return await this.models.NymSet.create({ uuid: uuid.v7(), public_key: publicKey, nym: nym, created_at: new Date().toISOString(), }); - } + }; - async function areProfileDetailsComplete(publicKey) { - const isNymSet = await models.NymSet.findOne({ + const areProfileDetailsComplete = async (publicKey) => { + console.log(this.models); + const isNymSet = await this.models.NymSet.findOne({ where: { public_key: publicKey }, }); - const areContactDetailsSet = await models.ContactDetailsSet.findOne({ + const areContactDetailsSet = await this.models.ContactDetailsSet.findOne({ where: { public_key: publicKey, }, }); return isNymSet && areContactDetailsSet; - } + }; return { setContactDetails, setNym, areProfileDetailsComplete }; } From bf0775ba31680d87b81fc61935b589ad36e5d619 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 16:06:44 +0100 Subject: [PATCH 166/239] offer service --- src/services/index.js | 5 ++++- src/services/offerService.js | 38 +++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index f71ab58..65dc649 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -45,7 +45,10 @@ class ServicesProvider { const ProfileServiceProvider = require('../services/profileService'); const profileService = new ProfileServiceProvider({ models }).provide(); const OfferServiceProvider = require('../services/offerService'); - const offerService = new OfferServiceProvider().provide(); + const offerService = new OfferServiceProvider({ + models, + errors, + }).provide(); return { invitesService, diff --git a/src/services/offerService.js b/src/services/offerService.js index 25f7b6d..b2696aa 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -1,19 +1,19 @@ const uuid = require('uuid'); -const models = require('../models'); - -const errors = require('../errors'); - class OfferServiceProvider { + constructor({ models, errors }) { + this.models = models; + this.errors = errors; + } provide() { - async function createOffer(publicKey, offerDetails) { - const offerCreated = await models.OfferCreated.create({ + const createOffer = async (publicKey, offerDetails) => { + const offerCreated = await this.models.OfferCreated.create({ uuid: uuid.v7(), public_key: publicKey, created_at: new Date().toISOString(), }); - await models.OfferDetailsSet.create({ + await this.models.OfferDetailsSet.create({ uuid: uuid.v7(), offer_uuid: offerCreated.uuid, wants: offerDetails.wants, @@ -30,29 +30,31 @@ class OfferServiceProvider { are_big_notes_accepted: offerDetails.are_big_notes_accepted, created_at: new Date().toISOString(), }); - } + }; - async function deleteOffer(offerUuid) { + const deleteOffer = async (offerUuid) => { const offerExists = Boolean( - await models.OfferCreated.findOne({ where: { uuid: offerUuid } }) + await this.models.OfferCreated.findOne({ where: { uuid: offerUuid } }) ); const offerHasBeenDeleted = Boolean( - await models.OfferDeleted.findOne({ where: { offer_uuid: offerUuid } }) + await this.models.OfferDeleted.findOne({ + where: { offer_uuid: offerUuid }, + }) ); if (!offerExists || offerHasBeenDeleted) { - throw new errors.NotFoundError(`Could not find the offer.`); + throw new this.errors.NotFoundError(`Could not find the offer.`); } - return models.OfferDeleted.create({ + return this.models.OfferDeleted.create({ uuid: uuid.v7(), offer_uuid: offerUuid, created_at: new Date().toISOString(), }); - } + }; - async function getOffersByPublicKey(publicKey) { - const offers = await models.OfferCreated.findAll({ + const getOffersByPublicKey = async (publicKey) => { + const offers = await this.models.OfferCreated.findAll({ where: { public_key: publicKey, }, @@ -67,7 +69,7 @@ class OfferServiceProvider { const offersToReturn = []; if (offers) { for (const someOffer of offers) { - const offerDetails = await models.OfferDetailsSet.findOne({ + const offerDetails = await this.models.OfferDetailsSet.findOne({ where: { offer_uuid: someOffer.uuid, }, @@ -96,7 +98,7 @@ class OfferServiceProvider { } return offersToReturn; - } + }; return { createOffer, getOffersByPublicKey, deleteOffer }; } From ee590984b223bbd206fb3cbd65a5c2ea37a69041 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 16:06:55 +0100 Subject: [PATCH 167/239] remove comments --- src/services/index.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/services/index.js b/src/services/index.js index 65dc649..34a00e4 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -6,13 +6,6 @@ class ServicesProvider { constructor() {} provide() { - /* - Up next: - - start passing all service stuff from outside - - also remember to get CLi fixed and working - */ - - //done const NostrServiceProvider = require('../services/nostrService'); const nostrService = new NostrServiceProvider({ models: models, From b1ff3b8d759be47fb3216846edf24b3be610f21e Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 16:14:04 +0100 Subject: [PATCH 168/239] remove unused require --- src/services/profileService.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/services/profileService.js b/src/services/profileService.js index 391d953..aca4247 100644 --- a/src/services/profileService.js +++ b/src/services/profileService.js @@ -1,7 +1,5 @@ const uuid = require('uuid'); -const models = require('../models'); - class ProfileServiceProvider { constructor({ models }) { this.models = models; From b5c27c9b26b8086b42132630fc217df84c874ff2 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 7 Mar 2025 16:24:51 +0100 Subject: [PATCH 169/239] pass models from dependencies --- src/dependencies.js | 7 ++++++- src/services/index.js | 36 +++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index ce71d18..fa7afeb 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -4,9 +4,14 @@ function buildDependencies() { const dependencies = {}; const errors = require('./errors'); const constants = require('./constants'); + const models = require('./models'); const ServicesProvider = require('./services'); - const services = new ServicesProvider().provide(); + const services = new ServicesProvider({ + models, + constants, + errors, + }).provide(); dependencies.services = services; const MiddlewaresProvider = require('./middlewares'); diff --git a/src/services/index.js b/src/services/index.js index 34a00e4..d4406fb 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,46 +1,48 @@ -const models = require('../models'); -const constants = require('../constants'); -const errors = require('../errors'); - class ServicesProvider { - constructor() {} + constructor({ models, constants, errors }) { + this.models = models; + this.constants = constants; + this.errors = errors; + } provide() { const NostrServiceProvider = require('../services/nostrService'); const nostrService = new NostrServiceProvider({ - models: models, - constants: constants, - errors: errors, + models: this.models, + constants: this.constants, + errors: this.errors, }).provide(); const InvitesServiceProvider = require('../services/invitesService'); const invitesService = new InvitesServiceProvider({ - models: models, - errors: errors, + models: this.models, + errors: this.errors, nostrService: nostrService, }).provide(); const LoginServiceProvider = require('../services/loginService'); const loginService = new LoginServiceProvider({ - models, - errors, + models: this.models, + errors: this.errors, nostrService, invitesService, }).provide(); const SessionServiceProvider = require('../services/sessionService'); const sessionService = new SessionServiceProvider({ - models, - constants, + models: this.models, + constants: this.constants, invitesService, }).provide(); const ProfileServiceProvider = require('../services/profileService'); - const profileService = new ProfileServiceProvider({ models }).provide(); + const profileService = new ProfileServiceProvider({ + models: this.models, + }).provide(); const OfferServiceProvider = require('../services/offerService'); const offerService = new OfferServiceProvider({ - models, - errors, + models: this.models, + errors: this.errors, }).provide(); return { From be7ec9b43fc1f74d01fbb465e18b10ef03dc8cc1 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 8 Mar 2025 00:25:56 +0100 Subject: [PATCH 170/239] mass refactor of models --- src/dependencies.js | 4 +- src/models/AppInviteCreated.js | 57 ++++++---- src/models/ContactDetailsSet.js | 65 ++++++----- src/models/LoginChallengeCompleted.js | 65 ++++++----- src/models/LoginChallengeCreated.js | 57 ++++++---- src/models/NostrChallengeCompleted.js | 73 ++++++------ src/models/NostrChallengeCreated.js | 65 ++++++----- src/models/NymSet.js | 65 ++++++----- src/models/OfferCreated.js | 57 ++++++---- src/models/OfferDeleted.js | 57 ++++++---- src/models/OfferDetailsSet.js | 145 +++++++++++++----------- src/models/SessionCreated.js | 57 ++++++---- src/models/SessionRelatedToPublickey.js | 64 ++++++----- src/models/SignUpChallengeCompleted.js | 73 ++++++------ src/models/SignUpChallengeCreated.js | 66 ++++++----- src/models/index.js | 122 +++++++++++++++----- 16 files changed, 627 insertions(+), 465 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index fa7afeb..8b4eef3 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -4,7 +4,9 @@ function buildDependencies() { const dependencies = {}; const errors = require('./errors'); const constants = require('./constants'); - const models = require('./models'); + + const ModelsProvider = require('./models'); + const models = new ModelsProvider().provide(); const ServicesProvider = require('./services'); const services = new ServicesProvider({ diff --git a/src/models/AppInviteCreated.js b/src/models/AppInviteCreated.js index 49967b3..4062fb5 100644 --- a/src/models/AppInviteCreated.js +++ b/src/models/AppInviteCreated.js @@ -1,27 +1,34 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const AppInviteCreated = sequelize.define( - 'AppInviteCreated', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - inviter_pub_key: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'app_invite_created', +class AppInviteCreatedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = AppInviteCreated; + provide() { + const AppInviteCreated = this.sequelize.define( + 'AppInviteCreated', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + inviter_pub_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'app_invite_created', + } + ); + return AppInviteCreated; + } +} + +module.exports = AppInviteCreatedProvider; diff --git a/src/models/ContactDetailsSet.js b/src/models/ContactDetailsSet.js index 8722e89..4a1a6a1 100644 --- a/src/models/ContactDetailsSet.js +++ b/src/models/ContactDetailsSet.js @@ -1,31 +1,38 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const ContactDetailsSet = sequelize.define( - 'ContactDetailsSet', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - encrypted_contact_details: { - type: DataTypes.TEXT, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'contact_details_set', +class ContactDetailsSetProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = ContactDetailsSet; + provide() { + const ContactDetailsSet = this.sequelize.define( + 'ContactDetailsSet', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + encrypted_contact_details: { + type: this.DataTypes.TEXT, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'contact_details_set', + } + ); + return ContactDetailsSet; + } +} + +module.exports = ContactDetailsSetProvider; diff --git a/src/models/LoginChallengeCompleted.js b/src/models/LoginChallengeCompleted.js index b4774bb..bce56e8 100644 --- a/src/models/LoginChallengeCompleted.js +++ b/src/models/LoginChallengeCompleted.js @@ -1,31 +1,38 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const LoginChallengeCompleted = sequelize.define( - 'LoginChallengeCompleted', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - nostr_challenge_completed_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'login_challenge_completed', +class LoginChallengeCompletedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = LoginChallengeCompleted; + provide() { + const LoginChallengeCompleted = this.sequelize.define( + 'LoginChallengeCompleted', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_completed_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'login_challenge_completed', + } + ); + return LoginChallengeCompleted; + } +} + +module.exports = LoginChallengeCompletedProvider; diff --git a/src/models/LoginChallengeCreated.js b/src/models/LoginChallengeCreated.js index c1c1792..3d29c16 100644 --- a/src/models/LoginChallengeCreated.js +++ b/src/models/LoginChallengeCreated.js @@ -1,27 +1,34 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const LoginChallengeCreated = sequelize.define( - 'LoginChallengeCreated', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - nostr_challenge_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'login_challenge_created', +class LoginChallengeCreatedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = LoginChallengeCreated; + provide() { + const LoginChallengeCreated = this.sequelize.define( + 'LoginChallengeCreated', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'login_challenge_created', + } + ); + return LoginChallengeCreated; + } +} + +module.exports = LoginChallengeCreatedProvider; diff --git a/src/models/NostrChallengeCompleted.js b/src/models/NostrChallengeCompleted.js index a656edd..b57196b 100644 --- a/src/models/NostrChallengeCompleted.js +++ b/src/models/NostrChallengeCompleted.js @@ -1,35 +1,42 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const NostrChallengeCompleted = sequelize.define( - 'NostrChallengeCompleted', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - challenge: { - type: DataTypes.STRING, - allowNull: false, - }, - signed_event: { - type: DataTypes.JSONB, - allowNull: false, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'nostr_challenge_completed', +class NostrChallengeCompletedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = NostrChallengeCompleted; + provide() { + const NostrChallengeCompleted = this.sequelize.define( + 'NostrChallengeCompleted', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + challenge: { + type: this.DataTypes.STRING, + allowNull: false, + }, + signed_event: { + type: this.DataTypes.JSONB, + allowNull: false, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'nostr_challenge_completed', + } + ); + return NostrChallengeCompleted; + } +} + +module.exports = NostrChallengeCompletedProvider; diff --git a/src/models/NostrChallengeCreated.js b/src/models/NostrChallengeCreated.js index 97a4c64..f0f0e91 100644 --- a/src/models/NostrChallengeCreated.js +++ b/src/models/NostrChallengeCreated.js @@ -1,31 +1,38 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const NostrChallengeCreated = sequelize.define( - 'NostrChallengeCreated', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - challenge: { - type: DataTypes.STRING, - allowNull: false, - }, - expires_at: { - type: DataTypes.DATE, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'nostr_challenge_created', +class NostrChallengeCreatedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = NostrChallengeCreated; + provide() { + const NostrChallengeCreated = this.sequelize.define( + 'NostrChallengeCreated', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + challenge: { + type: this.DataTypes.STRING, + allowNull: false, + }, + expires_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'nostr_challenge_created', + } + ); + return NostrChallengeCreated; + } +} + +module.exports = NostrChallengeCreatedProvider; diff --git a/src/models/NymSet.js b/src/models/NymSet.js index d59a310..4a9f26e 100644 --- a/src/models/NymSet.js +++ b/src/models/NymSet.js @@ -1,31 +1,38 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const NymSet = sequelize.define( - 'NymSet', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - nym: { - type: DataTypes.TEXT, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'nym_set', +class NymSetProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = NymSet; + provide() { + const NymSet = this.sequelize.define( + 'NymSet', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + nym: { + type: this.DataTypes.TEXT, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'nym_set', + } + ); + return NymSet; + } +} + +module.exports = NymSetProvider; diff --git a/src/models/OfferCreated.js b/src/models/OfferCreated.js index fd67249..13eeb54 100644 --- a/src/models/OfferCreated.js +++ b/src/models/OfferCreated.js @@ -1,27 +1,34 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const OfferCreated = sequelize.define( - 'OfferCreated', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'offer_created', +class OfferCreatedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = OfferCreated; + provide() { + const OfferCreated = this.sequelize.define( + 'OfferCreated', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'offer_created', + } + ); + return OfferCreated; + } +} + +module.exports = OfferCreatedProvider; diff --git a/src/models/OfferDeleted.js b/src/models/OfferDeleted.js index aa1b71a..6324583 100644 --- a/src/models/OfferDeleted.js +++ b/src/models/OfferDeleted.js @@ -1,27 +1,34 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const OfferDeleted = sequelize.define( - 'OfferDeleted', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - offer_uuid: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'offer_deleted', +class OfferDeletedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = OfferDeleted; + provide() { + const OfferDeleted = this.sequelize.define( + 'OfferDeleted', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + offer_uuid: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'offer_deleted', + } + ); + return OfferDeleted; + } +} + +module.exports = OfferDeletedProvider; diff --git a/src/models/OfferDetailsSet.js b/src/models/OfferDetailsSet.js index 20ac456..766f148 100644 --- a/src/models/OfferDetailsSet.js +++ b/src/models/OfferDetailsSet.js @@ -1,71 +1,78 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const OfferDetailsSet = sequelize.define( - 'OfferDetailsSet', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - offer_uuid: { - type: DataTypes.STRING, - allowNull: false, - }, - wants: { - type: DataTypes.STRING, - allowNull: false, - }, - premium: { - type: DataTypes.DECIMAL(5, 2), - allowNull: false, - }, - trade_amount_eur: { - type: DataTypes.INTEGER, - allowNull: false, - }, - location_details: { - type: DataTypes.TEXT, - allowNull: false, - }, - time_availability_details: { - type: DataTypes.TEXT, - allowNull: false, - }, - show_offer_to_trusted: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - show_offer_to_trusted_trusted: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - show_offer_to_all_members: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - is_onchain_accepted: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - is_lightning_accepted: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - are_big_notes_accepted: { - type: DataTypes.BOOLEAN, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'offer_details_set', +class OfferDetailsSetProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = OfferDetailsSet; + provide() { + const OfferDetailsSet = this.sequelize.define( + 'OfferDetailsSet', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + offer_uuid: { + type: this.DataTypes.STRING, + allowNull: false, + }, + wants: { + type: this.DataTypes.STRING, + allowNull: false, + }, + premium: { + type: this.DataTypes.DECIMAL(5, 2), + allowNull: false, + }, + trade_amount_eur: { + type: this.DataTypes.INTEGER, + allowNull: false, + }, + location_details: { + type: this.DataTypes.TEXT, + allowNull: false, + }, + time_availability_details: { + type: this.DataTypes.TEXT, + allowNull: false, + }, + show_offer_to_trusted: { + type: this.DataTypes.BOOLEAN, + allowNull: false, + }, + show_offer_to_trusted_trusted: { + type: this.DataTypes.BOOLEAN, + allowNull: false, + }, + show_offer_to_all_members: { + type: this.DataTypes.BOOLEAN, + allowNull: false, + }, + is_onchain_accepted: { + type: this.DataTypes.BOOLEAN, + allowNull: false, + }, + is_lightning_accepted: { + type: this.DataTypes.BOOLEAN, + allowNull: false, + }, + are_big_notes_accepted: { + type: this.DataTypes.BOOLEAN, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'offer_details_set', + } + ); + return OfferDetailsSet; + } +} + +module.exports = OfferDetailsSetProvider; diff --git a/src/models/SessionCreated.js b/src/models/SessionCreated.js index 8d0de50..a97c1fb 100644 --- a/src/models/SessionCreated.js +++ b/src/models/SessionCreated.js @@ -1,27 +1,34 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const SessionCreated = sequelize.define( - 'SessionCreated', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - expires_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'session_created', +class SessionCreatedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = SessionCreated; + provide() { + const SessionCreated = this.sequelize.define( + 'SessionCreated', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + expires_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'session_created', + } + ); + return SessionCreated; + } +} + +module.exports = SessionCreatedProvider; diff --git a/src/models/SessionRelatedToPublickey.js b/src/models/SessionRelatedToPublickey.js index 4802267..321da32 100644 --- a/src/models/SessionRelatedToPublickey.js +++ b/src/models/SessionRelatedToPublickey.js @@ -1,31 +1,37 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const SessionRelatedToPublickey = sequelize.define( - 'SessionRelatedToPublickey', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - session_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'session_related_to_public_key', +class SessionRelatedToPublickeyProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = SessionRelatedToPublickey; + provide() { + const SessionRelatedToPublickey = this.sequelize.define( + 'SessionRelatedToPublickey', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + session_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'session_related_to_public_key', + } + ); + return SessionRelatedToPublickey; + } +} +module.exports = SessionRelatedToPublickeyProvider; diff --git a/src/models/SignUpChallengeCompleted.js b/src/models/SignUpChallengeCompleted.js index 2960421..afd65f2 100644 --- a/src/models/SignUpChallengeCompleted.js +++ b/src/models/SignUpChallengeCompleted.js @@ -1,35 +1,42 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const SignUpChallengeCompleted = sequelize.define( - 'SignUpChallengeCompleted', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - nostr_challenge_completed_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - app_invite_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - public_key: { - type: DataTypes.STRING, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'sign_up_challenge_completed', +class SignUpChallengeCompletedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = SignUpChallengeCompleted; + provide() { + const SignUpChallengeCompleted = this.sequelize.define( + 'SignUpChallengeCompleted', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_completed_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + app_invite_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + public_key: { + type: this.DataTypes.STRING, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'sign_up_challenge_completed', + } + ); + return SignUpChallengeCompleted; + } +} + +module.exports = SignUpChallengeCompletedProvider; diff --git a/src/models/SignUpChallengeCreated.js b/src/models/SignUpChallengeCreated.js index 1d3e22e..3da9ec4 100644 --- a/src/models/SignUpChallengeCreated.js +++ b/src/models/SignUpChallengeCreated.js @@ -1,31 +1,39 @@ -const { DataTypes } = require('sequelize'); -const sequelize = require('../database'); - -const SignUpChallengeCreated = sequelize.define( - 'SignUpChallengeCreated', - { - uuid: { - type: DataTypes.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - nostr_challenge_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - app_invite_uuid: { - type: DataTypes.UUID, - allowNull: false, - }, - created_at: { - type: DataTypes.DATE, - allowNull: false, - }, - }, - { - tableName: 'sign_up_challenge_created', +class SignUpChallengeCreatedProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; } -); -module.exports = SignUpChallengeCreated; + provide() { + const SignUpChallengeCreated = this.sequelize.define( + 'SignUpChallengeCreated', + { + uuid: { + type: this.DataTypes.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + app_invite_uuid: { + type: this.DataTypes.UUID, + allowNull: false, + }, + created_at: { + type: this.DataTypes.DATE, + allowNull: false, + }, + }, + { + tableName: 'sign_up_challenge_created', + } + ); + + return SignUpChallengeCreated; + } +} + +module.exports = SignUpChallengeCreatedProvider; diff --git a/src/models/index.js b/src/models/index.js index ed7ed30..1d3da51 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -1,31 +1,93 @@ -const AppInviteCreated = require('./AppInviteCreated'); -const ContactDetailsSet = require('./ContactDetailsSet'); -const LoginChallengeCompleted = require('./LoginChallengeCompleted'); -const LoginChallengeCreated = require('./LoginChallengeCreated'); -const NostrChallengeCompleted = require('./NostrChallengeCompleted'); -const NostrChallengeCreated = require('./NostrChallengeCreated'); -const NymSet = require('./NymSet'); -const OfferCreated = require('./OfferCreated'); -const OfferDeleted = require('./OfferDeleted'); -const OfferDetailsSet = require('./OfferDetailsSet'); -const SessionCreated = require('./SessionCreated'); -const SessionRelatedToPublickey = require('./SessionRelatedToPublickey'); -const SignUpChallengeCompleted = require('./SignUpChallengeCompleted'); -const SignUpChallengeCreated = require('./SignUpChallengeCreated'); +const sequelize = require('../database'); +const { DataTypes } = require('sequelize'); -module.exports = { - SignUpChallengeCreated, - SignUpChallengeCompleted, - SessionRelatedToPublickey, - SessionCreated, - OfferDeleted, - OfferDetailsSet, - OfferCreated, - NymSet, - NostrChallengeCreated, - NostrChallengeCompleted, - LoginChallengeCompleted, - LoginChallengeCreated, - ContactDetailsSet, - AppInviteCreated, -}; +const AppInviteCreatedProvider = require('./AppInviteCreated'); +const AppInviteCreated = new AppInviteCreatedProvider({ + sequelize, + DataTypes, +}).provide(); +const ContactDetailsSetProvider = require('./ContactDetailsSet'); +const ContactDetailsSet = new ContactDetailsSetProvider({ + sequelize, + DataTypes, +}).provide(); +const LoginChallengeCompletedProvider = require('./LoginChallengeCompleted'); +const LoginChallengeCompleted = new LoginChallengeCompletedProvider({ + sequelize, + DataTypes, +}).provide(); +const LoginChallengeCreatedProvider = require('./LoginChallengeCreated'); +const LoginChallengeCreated = new LoginChallengeCreatedProvider({ + sequelize, + DataTypes, +}).provide(); +const NostrChallengeCompletedProvider = require('./NostrChallengeCompleted'); +const NostrChallengeCompleted = new NostrChallengeCompletedProvider({ + sequelize, + DataTypes, +}).provide(); +const NostrChallengeCreatedProvider = require('./NostrChallengeCreated'); +const NostrChallengeCreated = new NostrChallengeCreatedProvider({ + sequelize, + DataTypes, +}).provide(); +const NymSetProvider = require('./NymSet'); +const NymSet = new NymSetProvider({ sequelize, DataTypes }).provide(); +const OfferCreatedProvider = require('./OfferCreated'); +const OfferCreated = new OfferCreatedProvider({ + sequelize, + DataTypes, +}).provide(); +const OfferDeletedProvider = require('./OfferDeleted'); +const OfferDeleted = new OfferDeletedProvider({ + sequelize, + DataTypes, +}).provide(); +const OfferDetailsSetProvider = require('./OfferDetailsSet'); +const OfferDetailsSet = new OfferDetailsSetProvider({ + sequelize, + DataTypes, +}).provide(); +const SessionCreatedProvider = require('./SessionCreated'); +const SessionCreated = new SessionCreatedProvider({ + sequelize, + DataTypes, +}).provide(); +const SessionRelatedToPublickeyProvider = require('./SessionRelatedToPublickey'); +const SessionRelatedToPublickey = new SessionRelatedToPublickeyProvider({ + sequelize, + DataTypes, +}).provide(); +const SignUpChallengeCompletedProvider = require('./SignUpChallengeCompleted'); +const SignUpChallengeCompleted = new SignUpChallengeCompletedProvider({ + sequelize, + DataTypes, +}).provide(); +const SignUpChallengeCreatedProvider = require('./SignUpChallengeCreated'); +const SignUpChallengeCreated = new SignUpChallengeCreatedProvider({ + sequelize, + DataTypes, +}).provide(); + +class ModelsProvider { + provide() { + return { + SignUpChallengeCreated, + SignUpChallengeCompleted, + SessionRelatedToPublickey, + SessionCreated, + OfferDeleted, + OfferDetailsSet, + OfferCreated, + NymSet, + NostrChallengeCreated, + NostrChallengeCompleted, + LoginChallengeCompleted, + LoginChallengeCreated, + ContactDetailsSet, + AppInviteCreated, + }; + } +} + +module.exports = ModelsProvider; From b4d698a989d1f0125b20214adcc8cc79a9e1e22c Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 8 Mar 2025 00:26:14 +0100 Subject: [PATCH 171/239] move declarations inside provider --- src/models/index.js | 136 ++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/models/index.js b/src/models/index.js index 1d3da51..16e3dec 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -1,76 +1,76 @@ const sequelize = require('../database'); const { DataTypes } = require('sequelize'); -const AppInviteCreatedProvider = require('./AppInviteCreated'); -const AppInviteCreated = new AppInviteCreatedProvider({ - sequelize, - DataTypes, -}).provide(); -const ContactDetailsSetProvider = require('./ContactDetailsSet'); -const ContactDetailsSet = new ContactDetailsSetProvider({ - sequelize, - DataTypes, -}).provide(); -const LoginChallengeCompletedProvider = require('./LoginChallengeCompleted'); -const LoginChallengeCompleted = new LoginChallengeCompletedProvider({ - sequelize, - DataTypes, -}).provide(); -const LoginChallengeCreatedProvider = require('./LoginChallengeCreated'); -const LoginChallengeCreated = new LoginChallengeCreatedProvider({ - sequelize, - DataTypes, -}).provide(); -const NostrChallengeCompletedProvider = require('./NostrChallengeCompleted'); -const NostrChallengeCompleted = new NostrChallengeCompletedProvider({ - sequelize, - DataTypes, -}).provide(); -const NostrChallengeCreatedProvider = require('./NostrChallengeCreated'); -const NostrChallengeCreated = new NostrChallengeCreatedProvider({ - sequelize, - DataTypes, -}).provide(); -const NymSetProvider = require('./NymSet'); -const NymSet = new NymSetProvider({ sequelize, DataTypes }).provide(); -const OfferCreatedProvider = require('./OfferCreated'); -const OfferCreated = new OfferCreatedProvider({ - sequelize, - DataTypes, -}).provide(); -const OfferDeletedProvider = require('./OfferDeleted'); -const OfferDeleted = new OfferDeletedProvider({ - sequelize, - DataTypes, -}).provide(); -const OfferDetailsSetProvider = require('./OfferDetailsSet'); -const OfferDetailsSet = new OfferDetailsSetProvider({ - sequelize, - DataTypes, -}).provide(); -const SessionCreatedProvider = require('./SessionCreated'); -const SessionCreated = new SessionCreatedProvider({ - sequelize, - DataTypes, -}).provide(); -const SessionRelatedToPublickeyProvider = require('./SessionRelatedToPublickey'); -const SessionRelatedToPublickey = new SessionRelatedToPublickeyProvider({ - sequelize, - DataTypes, -}).provide(); -const SignUpChallengeCompletedProvider = require('./SignUpChallengeCompleted'); -const SignUpChallengeCompleted = new SignUpChallengeCompletedProvider({ - sequelize, - DataTypes, -}).provide(); -const SignUpChallengeCreatedProvider = require('./SignUpChallengeCreated'); -const SignUpChallengeCreated = new SignUpChallengeCreatedProvider({ - sequelize, - DataTypes, -}).provide(); - class ModelsProvider { provide() { + const AppInviteCreatedProvider = require('./AppInviteCreated'); + const AppInviteCreated = new AppInviteCreatedProvider({ + sequelize, + DataTypes, + }).provide(); + const ContactDetailsSetProvider = require('./ContactDetailsSet'); + const ContactDetailsSet = new ContactDetailsSetProvider({ + sequelize, + DataTypes, + }).provide(); + const LoginChallengeCompletedProvider = require('./LoginChallengeCompleted'); + const LoginChallengeCompleted = new LoginChallengeCompletedProvider({ + sequelize, + DataTypes, + }).provide(); + const LoginChallengeCreatedProvider = require('./LoginChallengeCreated'); + const LoginChallengeCreated = new LoginChallengeCreatedProvider({ + sequelize, + DataTypes, + }).provide(); + const NostrChallengeCompletedProvider = require('./NostrChallengeCompleted'); + const NostrChallengeCompleted = new NostrChallengeCompletedProvider({ + sequelize, + DataTypes, + }).provide(); + const NostrChallengeCreatedProvider = require('./NostrChallengeCreated'); + const NostrChallengeCreated = new NostrChallengeCreatedProvider({ + sequelize, + DataTypes, + }).provide(); + const NymSetProvider = require('./NymSet'); + const NymSet = new NymSetProvider({ sequelize, DataTypes }).provide(); + const OfferCreatedProvider = require('./OfferCreated'); + const OfferCreated = new OfferCreatedProvider({ + sequelize, + DataTypes, + }).provide(); + const OfferDeletedProvider = require('./OfferDeleted'); + const OfferDeleted = new OfferDeletedProvider({ + sequelize, + DataTypes, + }).provide(); + const OfferDetailsSetProvider = require('./OfferDetailsSet'); + const OfferDetailsSet = new OfferDetailsSetProvider({ + sequelize, + DataTypes, + }).provide(); + const SessionCreatedProvider = require('./SessionCreated'); + const SessionCreated = new SessionCreatedProvider({ + sequelize, + DataTypes, + }).provide(); + const SessionRelatedToPublickeyProvider = require('./SessionRelatedToPublickey'); + const SessionRelatedToPublickey = new SessionRelatedToPublickeyProvider({ + sequelize, + DataTypes, + }).provide(); + const SignUpChallengeCompletedProvider = require('./SignUpChallengeCompleted'); + const SignUpChallengeCompleted = new SignUpChallengeCompletedProvider({ + sequelize, + DataTypes, + }).provide(); + const SignUpChallengeCreatedProvider = require('./SignUpChallengeCreated'); + const SignUpChallengeCreated = new SignUpChallengeCreatedProvider({ + sequelize, + DataTypes, + }).provide(); + return { SignUpChallengeCreated, SignUpChallengeCompleted, From bf478bbbe912fa1c681b91163184a7f6c23a1f4c Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 8 Mar 2025 00:29:52 +0100 Subject: [PATCH 172/239] pull up --- src/dependencies.js | 4 ++- src/models/index.js | 64 ++++++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index 8b4eef3..6c68534 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -5,8 +5,10 @@ function buildDependencies() { const errors = require('./errors'); const constants = require('./constants'); + const sequelize = require('./database'); + const { DataTypes } = require('sequelize'); const ModelsProvider = require('./models'); - const models = new ModelsProvider().provide(); + const models = new ModelsProvider({ sequelize, DataTypes }).provide(); const ServicesProvider = require('./services'); const services = new ServicesProvider({ diff --git a/src/models/index.js b/src/models/index.js index 16e3dec..02b4028 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -1,74 +1,78 @@ -const sequelize = require('../database'); -const { DataTypes } = require('sequelize'); - class ModelsProvider { + constructor({ sequelize, DataTypes }) { + this.sequelize = sequelize; + this.DataTypes = DataTypes; + } provide() { const AppInviteCreatedProvider = require('./AppInviteCreated'); const AppInviteCreated = new AppInviteCreatedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const ContactDetailsSetProvider = require('./ContactDetailsSet'); const ContactDetailsSet = new ContactDetailsSetProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const LoginChallengeCompletedProvider = require('./LoginChallengeCompleted'); const LoginChallengeCompleted = new LoginChallengeCompletedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const LoginChallengeCreatedProvider = require('./LoginChallengeCreated'); const LoginChallengeCreated = new LoginChallengeCreatedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const NostrChallengeCompletedProvider = require('./NostrChallengeCompleted'); const NostrChallengeCompleted = new NostrChallengeCompletedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const NostrChallengeCreatedProvider = require('./NostrChallengeCreated'); const NostrChallengeCreated = new NostrChallengeCreatedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const NymSetProvider = require('./NymSet'); - const NymSet = new NymSetProvider({ sequelize, DataTypes }).provide(); + const NymSet = new NymSetProvider({ + sequelize: this.sequelize, + DataTypes: this.DataTypes, + }).provide(); const OfferCreatedProvider = require('./OfferCreated'); const OfferCreated = new OfferCreatedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const OfferDeletedProvider = require('./OfferDeleted'); const OfferDeleted = new OfferDeletedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const OfferDetailsSetProvider = require('./OfferDetailsSet'); const OfferDetailsSet = new OfferDetailsSetProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const SessionCreatedProvider = require('./SessionCreated'); const SessionCreated = new SessionCreatedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const SessionRelatedToPublickeyProvider = require('./SessionRelatedToPublickey'); const SessionRelatedToPublickey = new SessionRelatedToPublickeyProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const SignUpChallengeCompletedProvider = require('./SignUpChallengeCompleted'); const SignUpChallengeCompleted = new SignUpChallengeCompletedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); const SignUpChallengeCreatedProvider = require('./SignUpChallengeCreated'); const SignUpChallengeCreated = new SignUpChallengeCreatedProvider({ - sequelize, - DataTypes, + sequelize: this.sequelize, + DataTypes: this.DataTypes, }).provide(); return { From 2d124a1ef4fd6c549bbae40541037f3c56b423d5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 8 Mar 2025 02:47:37 +0100 Subject: [PATCH 173/239] associations and db stuff --- src/associations.js | 19 +++++++++++++++++++ src/database.js | 13 ++----------- src/dependencies.js | 14 +++++++++++++- 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 src/associations.js diff --git a/src/associations.js b/src/associations.js new file mode 100644 index 0000000..08bf67a --- /dev/null +++ b/src/associations.js @@ -0,0 +1,19 @@ +class AssociationsDefiner { + constructor({ models, DataTypes }) { + this.models = models; + this.DataTypes = DataTypes; + } + + define() { + this.models.OfferCreated.hasOne(this.models.OfferDeleted); + this.models.OfferDeleted.belongsTo(this.models.OfferCreated, { + foreignKey: { + name: 'offer_uuid', + type: this.DataTypes.UUID, + allowNull: false, + }, + }); + } +} + +module.exports = AssociationsDefiner; diff --git a/src/database.js b/src/database.js index 133328f..ed4ef6c 100644 --- a/src/database.js +++ b/src/database.js @@ -10,11 +10,11 @@ const sequelize = new Sequelize({ database: process.env.POSTGRES_DB, username: process.env.POSTGRES_USER, password: process.env.POSTGRES_PASSWORD, - logging: (msg) => { + /* logging: (msg) => { if (msg && (msg.includes('ERROR') || msg.includes('error'))) { console.error(msg); } - }, + }, */ define: { timestamps: false, freezeTableName: true, @@ -23,13 +23,4 @@ const sequelize = new Sequelize({ }, }); -sequelize - .sync() - .then(() => { - console.log('Database synced'); - }) - .catch((err) => { - console.error('Error syncing the database:', err); - }); - module.exports = sequelize; diff --git a/src/dependencies.js b/src/dependencies.js index 6c68534..b85ad2e 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -1,6 +1,6 @@ const express = require('express'); -function buildDependencies() { +async function buildDependencies() { const dependencies = {}; const errors = require('./errors'); const constants = require('./constants'); @@ -10,6 +10,18 @@ function buildDependencies() { const ModelsProvider = require('./models'); const models = new ModelsProvider({ sequelize, DataTypes }).provide(); + const AssociationsDefiner = require('./associations'); + new AssociationsDefiner({ models, DataTypes }).define(); + + sequelize + .sync({ alter: true }) + .then(() => { + console.log('Database synced'); + }) + .catch((err) => { + console.error('Error syncing the database:', err); + }); + const ServicesProvider = require('./services'); const services = new ServicesProvider({ models, From 9660e263d1c032fe6d93b517cba4216f289550c2 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 8 Mar 2025 02:51:11 +0100 Subject: [PATCH 174/239] removed unwanted async --- src/dependencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dependencies.js b/src/dependencies.js index b85ad2e..606a0fe 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -1,6 +1,6 @@ const express = require('express'); -async function buildDependencies() { +function buildDependencies() { const dependencies = {}; const errors = require('./errors'); const constants = require('./constants'); From e718626aadf5516ffdc51b1a9b5004f8e4b4adbf Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 9 Mar 2025 16:48:37 +0100 Subject: [PATCH 175/239] now it works --- src/associations.js | 4 +++- src/models/OfferDeleted.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/associations.js b/src/associations.js index 08bf67a..b43246a 100644 --- a/src/associations.js +++ b/src/associations.js @@ -5,7 +5,9 @@ class AssociationsDefiner { } define() { - this.models.OfferCreated.hasOne(this.models.OfferDeleted); + this.models.OfferCreated.hasOne(this.models.OfferDeleted, { + foreignKey: 'offer_uuid', + }); this.models.OfferDeleted.belongsTo(this.models.OfferCreated, { foreignKey: { name: 'offer_uuid', diff --git a/src/models/OfferDeleted.js b/src/models/OfferDeleted.js index 6324583..019c61f 100644 --- a/src/models/OfferDeleted.js +++ b/src/models/OfferDeleted.js @@ -15,7 +15,7 @@ class OfferDeletedProvider { primaryKey: true, }, offer_uuid: { - type: this.DataTypes.STRING, + type: this.DataTypes.UUID, allowNull: false, }, created_at: { From bb68c0585ad2fce24a76a55577d6f2591a38f751 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 9 Mar 2025 16:49:20 +0100 Subject: [PATCH 176/239] no more sync --- src/dependencies.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index 606a0fe..f9d9aa8 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -13,7 +13,7 @@ function buildDependencies() { const AssociationsDefiner = require('./associations'); new AssociationsDefiner({ models, DataTypes }).define(); - sequelize + /* sequelize .sync({ alter: true }) .then(() => { console.log('Database synced'); @@ -21,7 +21,7 @@ function buildDependencies() { .catch((err) => { console.error('Error syncing the database:', err); }); - + */ const ServicesProvider = require('./services'); const services = new ServicesProvider({ models, From 12a0c7563e54c585b78297d3363ef788ec78985a Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 9 Mar 2025 16:49:51 +0100 Subject: [PATCH 177/239] add sequelize-cli --- package-lock.json | 832 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- 2 files changed, 829 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c6f215..2bea970 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,8 @@ "globals": "^15.15.0", "playwright": "^1.50.1", "prettier": "^3.5.1", - "prettier-plugin-ejs": "^1.0.3" + "prettier-plugin-ejs": "^1.0.3", + "sequelize-cli": "^6.6.2" } }, "node_modules/@eslint-community/eslint-utils": { @@ -193,6 +194,102 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@noble/ciphers": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", @@ -290,6 +387,22 @@ "node": ">=10" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -573,6 +686,15 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -615,6 +737,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -774,6 +902,33 @@ "node": ">=6" } }, + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -812,6 +967,16 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -883,6 +1048,19 @@ "node": ">= 8" } }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -989,6 +1167,63 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1012,7 +1247,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "optional": true + "devOptional": true }, "node_modules/encodeurl": { "version": "2.0.0", @@ -1093,6 +1328,67 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1275,6 +1571,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1337,6 +1648,16 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -1390,6 +1711,15 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1512,6 +1842,34 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==" }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1533,6 +1891,21 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -1591,6 +1964,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", @@ -1689,7 +2071,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "optional": true + "devOptional": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -1960,6 +2342,21 @@ "node": ">= 0.10" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1972,7 +2369,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "optional": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -2002,11 +2399,32 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -2024,6 +2442,113 @@ "node": ">=10" } }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2056,6 +2581,18 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2112,6 +2649,15 @@ "node": ">=10" } }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -2155,6 +2701,25 @@ "node": ">= 0.6" } }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dev": true, + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -2377,6 +2942,12 @@ "node": ">= 0.6" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true + }, "node_modules/node-abi": { "version": "3.74.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", @@ -2567,6 +3138,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2610,6 +3187,43 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -2849,6 +3463,12 @@ "node": ">=10" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2960,6 +3580,35 @@ "node": ">= 6" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3160,6 +3809,28 @@ } } }, + "node_modules/sequelize-cli": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.2.tgz", + "integrity": "sha512-V8Oh+XMz2+uquLZltZES6MVAD+yEnmMfwfn+gpXcDiwE3jyQygLt4xoI0zG8gKt6cRcs84hsKnXAKDQjG/JAgg==", + "dev": true, + "dependencies": { + "cli-color": "^2.0.3", + "fs-extra": "^9.1.0", + "js-beautify": "^1.14.5", + "lodash": "^4.17.21", + "resolve": "^1.22.1", + "umzug": "^2.3.0", + "yargs": "^16.2.0" + }, + "bin": { + "sequelize": "lib/sequelize", + "sequelize-cli": "lib/sequelize" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/sequelize-pool": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", @@ -3488,7 +4159,22 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "optional": true, + "devOptional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3509,6 +4195,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3528,6 +4227,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/synckit": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", @@ -3604,6 +4315,19 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3634,6 +4358,12 @@ "node": "*" } }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3668,6 +4398,18 @@ "node": ">= 0.6" } }, + "node_modules/umzug": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz", + "integrity": "sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==", + "dev": true, + "dependencies": { + "bluebird": "^3.7.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -3691,6 +4433,15 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3787,6 +4538,41 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3800,11 +4586,47 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 05c2f32..26f47dc 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "globals": "^15.15.0", "playwright": "^1.50.1", "prettier": "^3.5.1", - "prettier-plugin-ejs": "^1.0.3" + "prettier-plugin-ejs": "^1.0.3", + "sequelize-cli": "^6.6.2" } } From 55a57444f8bb264488de78558db71681ebf0f66b Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 9 Mar 2025 16:51:23 +0100 Subject: [PATCH 178/239] move database into folder --- src/{ => database}/database.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{ => database}/database.js (100%) diff --git a/src/database.js b/src/database/database.js similarity index 100% rename from src/database.js rename to src/database/database.js From 8e86f7297582a5802dfb06075ecc15f536dfb1fc Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 9 Mar 2025 17:09:40 +0100 Subject: [PATCH 179/239] start --- .sequelizerc | 6 ++++ src/database/config.js | 20 +++++++++++++ .../migrations/20250308000000-create-table.js | 30 +++++++++++++++++++ src/dependencies.js | 2 +- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 .sequelizerc create mode 100644 src/database/config.js create mode 100644 src/database/migrations/20250308000000-create-table.js diff --git a/.sequelizerc b/.sequelizerc new file mode 100644 index 0000000..c49d00f --- /dev/null +++ b/.sequelizerc @@ -0,0 +1,6 @@ +const path = require('path'); + +module.exports = { + config: path.resolve('src', 'database', 'config.js'), + 'migrations-path': path.resolve('src', 'database', 'migrations'), +}; diff --git a/src/database/config.js b/src/database/config.js new file mode 100644 index 0000000..a13f133 --- /dev/null +++ b/src/database/config.js @@ -0,0 +1,20 @@ +const dotenv = require('dotenv'); + +dotenv.config(); + +module.exports = { + development: { + dialect: 'postgres', + host: process.env.POSTGRES_HOST, + port: 5432, + database: process.env.POSTGRES_DB, + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + define: { + timestamps: false, + freezeTableName: true, + underscored: true, + quoteIdentifiers: false, + }, + }, +}; diff --git a/src/database/migrations/20250308000000-create-table.js b/src/database/migrations/20250308000000-create-table.js new file mode 100644 index 0000000..bb77410 --- /dev/null +++ b/src/database/migrations/20250308000000-create-table.js @@ -0,0 +1,30 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + firstName: { + type: Sequelize.STRING, + }, + lastName: { + type: Sequelize.STRING, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Users'); + }, +}; diff --git a/src/dependencies.js b/src/dependencies.js index f9d9aa8..a93620d 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -5,7 +5,7 @@ function buildDependencies() { const errors = require('./errors'); const constants = require('./constants'); - const sequelize = require('./database'); + const sequelize = require('./database/database'); const { DataTypes } = require('sequelize'); const ModelsProvider = require('./models'); const models = new ModelsProvider({ sequelize, DataTypes }).provide(); From f5ced9888e5032451adc88169f7cacac2d1455df Mon Sep 17 00:00:00 2001 From: counterweight Date: Sun, 9 Mar 2025 17:13:26 +0100 Subject: [PATCH 180/239] first schema --- .../migrations/20250308000000-create-table.js | 30 ------------------- .../migrations/20250308000000-first-schema.js | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 src/database/migrations/20250308000000-create-table.js create mode 100644 src/database/migrations/20250308000000-first-schema.js diff --git a/src/database/migrations/20250308000000-create-table.js b/src/database/migrations/20250308000000-create-table.js deleted file mode 100644 index bb77410..0000000 --- a/src/database/migrations/20250308000000-create-table.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; -module.exports = { - up: (queryInterface, Sequelize) => { - return queryInterface.createTable('Users', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - firstName: { - type: Sequelize.STRING, - }, - lastName: { - type: Sequelize.STRING, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }); - }, - down: (queryInterface, Sequelize) => { - return queryInterface.dropTable('Users'); - }, -}; diff --git a/src/database/migrations/20250308000000-first-schema.js b/src/database/migrations/20250308000000-first-schema.js new file mode 100644 index 0000000..3326bde --- /dev/null +++ b/src/database/migrations/20250308000000-first-schema.js @@ -0,0 +1,30 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable( + 'AppInviteCreated', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + inviter_pub_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { + tableName: 'app_invite_created', + } + ); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Users'); + }, +}; From 9d4967a41d65ff07012d78409009b208f97ff6ff Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 12:47:09 +0100 Subject: [PATCH 181/239] first couple models working, now let's add more --- .../migrations/20250308000000-first-schema.js | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/database/migrations/20250308000000-first-schema.js b/src/database/migrations/20250308000000-first-schema.js index 3326bde..a368e2f 100644 --- a/src/database/migrations/20250308000000-first-schema.js +++ b/src/database/migrations/20250308000000-first-schema.js @@ -1,28 +1,54 @@ 'use strict'; module.exports = { up: (queryInterface, Sequelize) => { - return queryInterface.createTable( - 'AppInviteCreated', - { - uuid: { - type: Sequelize.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - inviter_pub_key: { - type: Sequelize.STRING, - allowNull: false, - }, - created_at: { - type: Sequelize.DATE, - allowNull: false, - }, - }, - { - tableName: 'app_invite_created', - } - ); + return queryInterface.sequelize.transaction((t) => { + return Promise.all([ + queryInterface.createTable( + 'app_invite_created', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + inviter_pub_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), + queryInterface.createTable( + 'contact_details_set', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + encrypted_contact_details: { + type: Sequelize.TEXT, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), + ]); + }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Users'); From afc8a6d04d9fda0e66ff00c19125b57b772aba86 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:02:33 +0100 Subject: [PATCH 182/239] nostr challenges --- .../migrations/20250308000000-first-schema.js | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema.js b/src/database/migrations/20250308000000-first-schema.js index a368e2f..8e7bfe9 100644 --- a/src/database/migrations/20250308000000-first-schema.js +++ b/src/database/migrations/20250308000000-first-schema.js @@ -47,6 +47,111 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'login_challenge_completed', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_completed_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), + queryInterface.createTable( + 'login_challenge_created', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), + + queryInterface.createTable( + 'nostr_challenge_created', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + challenge: { + type: Sequelize.STRING, + allowNull: false, + unique: true, + }, + expires_at: { + type: Sequelize.DATE, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), + queryInterface.createTable( + 'nostr_challenge_completed', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + challenge: { + type: Sequelize.STRING, + allowNull: false, + unique: true, + references: { + model: { + tableName: 'nostr_challenge_created', + }, + key: 'challenge', + }, + }, + signed_event: { + type: Sequelize.JSONB, + allowNull: false, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, From 3b2edb4ca99a178fa1f7f493a47d3c5ca8bf6561 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:05:33 +0100 Subject: [PATCH 183/239] relationship --- src/associations.js | 15 +++++++++++++++ src/models/NostrChallengeCompleted.js | 1 + src/models/NostrChallengeCreated.js | 1 + 3 files changed, 17 insertions(+) diff --git a/src/associations.js b/src/associations.js index b43246a..45747be 100644 --- a/src/associations.js +++ b/src/associations.js @@ -5,6 +5,21 @@ class AssociationsDefiner { } define() { + this.models.NostrChallengeCreated.hasOne( + this.models.NostrChallengeCompleted, + { + foreignKey: 'challenge', + } + ); + this.models.NostrChallengeCompleted.belongsTo( + this.models.NostrChallengeCreated, + { + foreignKey: { + name: 'challenge', + }, + } + ); + this.models.OfferCreated.hasOne(this.models.OfferDeleted, { foreignKey: 'offer_uuid', }); diff --git a/src/models/NostrChallengeCompleted.js b/src/models/NostrChallengeCompleted.js index b57196b..e569097 100644 --- a/src/models/NostrChallengeCompleted.js +++ b/src/models/NostrChallengeCompleted.js @@ -17,6 +17,7 @@ class NostrChallengeCompletedProvider { challenge: { type: this.DataTypes.STRING, allowNull: false, + unique: true, }, signed_event: { type: this.DataTypes.JSONB, diff --git a/src/models/NostrChallengeCreated.js b/src/models/NostrChallengeCreated.js index f0f0e91..f35ddc7 100644 --- a/src/models/NostrChallengeCreated.js +++ b/src/models/NostrChallengeCreated.js @@ -17,6 +17,7 @@ class NostrChallengeCreatedProvider { challenge: { type: this.DataTypes.STRING, allowNull: false, + unique: true, }, expires_at: { type: this.DataTypes.DATE, From 317ca7ded2288e870ec8657f28f899d1ba892242 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:43:55 +0100 Subject: [PATCH 184/239] split FKs into another file --- src/database/config.js | 1 + ... => 20250308000000-first-schema-tables.js} | 69 +++++++++---------- .../20250308000001-first-schema-fks.js | 42 +++++++++++ 3 files changed, 74 insertions(+), 38 deletions(-) rename src/database/migrations/{20250308000000-first-schema.js => 20250308000000-first-schema-tables.js} (95%) create mode 100644 src/database/migrations/20250308000001-first-schema-fks.js diff --git a/src/database/config.js b/src/database/config.js index a13f133..2800b22 100644 --- a/src/database/config.js +++ b/src/database/config.js @@ -10,6 +10,7 @@ module.exports = { database: process.env.POSTGRES_DB, username: process.env.POSTGRES_USER, password: process.env.POSTGRES_PASSWORD, + logging: console.log, define: { timestamps: false, freezeTableName: true, diff --git a/src/database/migrations/20250308000000-first-schema.js b/src/database/migrations/20250308000000-first-schema-tables.js similarity index 95% rename from src/database/migrations/20250308000000-first-schema.js rename to src/database/migrations/20250308000000-first-schema-tables.js index 8e7bfe9..3407606 100644 --- a/src/database/migrations/20250308000000-first-schema.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -48,7 +48,7 @@ module.exports = { { transaction: t } ), queryInterface.createTable( - 'login_challenge_completed', + 'nostr_challenge_created', { uuid: { type: Sequelize.UUID, @@ -56,13 +56,14 @@ module.exports = { unique: true, primaryKey: true, }, - nostr_challenge_completed_uuid: { - type: Sequelize.UUID, - allowNull: false, - }, - public_key: { + challenge: { type: Sequelize.STRING, allowNull: false, + unique: true, + }, + expires_at: { + type: Sequelize.DATE, + allowNull: false, }, created_at: { type: Sequelize.DATE, @@ -91,32 +92,6 @@ module.exports = { }, { transaction: t } ), - - queryInterface.createTable( - 'nostr_challenge_created', - { - uuid: { - type: Sequelize.UUID, - allowNull: false, - unique: true, - primaryKey: true, - }, - challenge: { - type: Sequelize.STRING, - allowNull: false, - unique: true, - }, - expires_at: { - type: Sequelize.DATE, - allowNull: false, - }, - created_at: { - type: Sequelize.DATE, - allowNull: false, - }, - }, - { transaction: t } - ), queryInterface.createTable( 'nostr_challenge_completed', { @@ -130,12 +105,6 @@ module.exports = { type: Sequelize.STRING, allowNull: false, unique: true, - references: { - model: { - tableName: 'nostr_challenge_created', - }, - key: 'challenge', - }, }, signed_event: { type: Sequelize.JSONB, @@ -152,6 +121,30 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'login_challenge_completed', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_completed_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js new file mode 100644 index 0000000..7ede52a --- /dev/null +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -0,0 +1,42 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction((t) => { + return Promise.all([ + queryInterface.addConstraint('login_challenge_created', { + fields: ['nostr_challenge_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), + queryInterface.addConstraint('nostr_challenge_completed', { + fields: ['challenge'], + type: 'foreign key', + references: { + table: 'nostr_challenge_created', + field: 'challenge', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), + queryInterface.addConstraint('login_challenge_completed', { + fields: ['nostr_challenge_completed_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_completed', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), + ]); + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('Users'); + }, +}; From 8c4c9dfe991b3ca97b4809ddc30aeb4cf78a0f77 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:46:14 +0100 Subject: [PATCH 185/239] add nymset --- .../20250308000000-first-schema-tables.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index 3407606..cef411b 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -145,6 +145,30 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'nym_set', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + nym: { + type: Sequelize.TEXT, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, From 01c9fca0931862b7d5d1d1ac0ff8dbf78dbee4ca Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:51:13 +0100 Subject: [PATCH 186/239] offercreated offerdeleted --- .../20250308000000-first-schema-tables.js | 43 +++++++++++++++++++ .../20250308000001-first-schema-fks.js | 10 +++++ 2 files changed, 53 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index cef411b..16c5456 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -1,4 +1,7 @@ 'use strict'; + +const { query } = require('express'); + module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.sequelize.transaction((t) => { @@ -169,6 +172,46 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'offer_created', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), + queryInterface.createTable( + 'offer_deleted', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + offer_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js index 7ede52a..79d1544 100644 --- a/src/database/migrations/20250308000001-first-schema-fks.js +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -33,6 +33,16 @@ module.exports = { onDelete: 'cascade', onUpdate: 'cascade', }), + queryInterface.addConstraint('offer_deleted', { + fields: ['offer_uuid'], + type: 'foreign key', + references: { + table: 'offer_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), ]); }); }, From d3e419c98b19fabb09ef274b0cb27460b5a19037 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:54:08 +0100 Subject: [PATCH 187/239] offerdetailsset --- .../20250308000000-first-schema-tables.js | 64 +++++++++++++++++++ .../20250308000001-first-schema-fks.js | 10 +++ src/models/OfferDetailsSet.js | 2 +- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index 16c5456..8cb5e18 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -212,6 +212,70 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'offer_details_set', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + offer_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + wants: { + type: Sequelize.STRING, + allowNull: false, + }, + premium: { + type: Sequelize.DECIMAL(5, 2), + allowNull: false, + }, + trade_amount_eur: { + type: Sequelize.INTEGER, + allowNull: false, + }, + location_details: { + type: Sequelize.TEXT, + allowNull: false, + }, + time_availability_details: { + type: Sequelize.TEXT, + allowNull: false, + }, + show_offer_to_trusted: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + show_offer_to_trusted_trusted: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + show_offer_to_all_members: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + is_onchain_accepted: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + is_lightning_accepted: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + are_big_notes_accepted: { + type: Sequelize.BOOLEAN, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js index 79d1544..56137b2 100644 --- a/src/database/migrations/20250308000001-first-schema-fks.js +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -43,6 +43,16 @@ module.exports = { onDelete: 'cascade', onUpdate: 'cascade', }), + queryInterface.addConstraint('offer_details_set', { + fields: ['offer_uuid'], + type: 'foreign key', + references: { + table: 'offer_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), ]); }); }, diff --git a/src/models/OfferDetailsSet.js b/src/models/OfferDetailsSet.js index 766f148..d2023d7 100644 --- a/src/models/OfferDetailsSet.js +++ b/src/models/OfferDetailsSet.js @@ -15,7 +15,7 @@ class OfferDetailsSetProvider { primaryKey: true, }, offer_uuid: { - type: this.DataTypes.STRING, + type: this.DataTypes.UUID, allowNull: false, }, wants: { From bccde12a046667a76ad539450e2dea2f5533af1d Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:56:00 +0100 Subject: [PATCH 188/239] session created --- .../20250308000000-first-schema-tables.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index 8cb5e18..60a4dd7 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -276,6 +276,26 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'session_created', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + expires_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, From 78362f1067624460817de66fb19706049141dd6f Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 13:57:35 +0100 Subject: [PATCH 189/239] session related to publickey --- .../20250308000000-first-schema-tables.js | 24 +++++++++++++++++++ .../20250308000001-first-schema-fks.js | 10 ++++++++ 2 files changed, 34 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index 60a4dd7..7506bb8 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -296,6 +296,30 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'session_related_to_public_key', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + session_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js index 56137b2..54c86b6 100644 --- a/src/database/migrations/20250308000001-first-schema-fks.js +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -53,6 +53,16 @@ module.exports = { onDelete: 'cascade', onUpdate: 'cascade', }), + queryInterface.addConstraint('session_related_to_public_key', { + fields: ['session_uuid'], + type: 'foreign key', + references: { + table: 'session_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), ]); }); }, From 3e0a4772aaf7fac7cbc1095dd871f139def40b47 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 14:02:41 +0100 Subject: [PATCH 190/239] signupchallengecreated --- .../20250308000000-first-schema-tables.js | 24 +++++++++++++++++++ .../20250308000001-first-schema-fks.js | 20 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index 7506bb8..73cba27 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -320,6 +320,30 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'sign_up_challenge_created', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + app_invite_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js index 54c86b6..573d238 100644 --- a/src/database/migrations/20250308000001-first-schema-fks.js +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -63,6 +63,26 @@ module.exports = { onDelete: 'cascade', onUpdate: 'cascade', }), + queryInterface.addConstraint('sign_up_challenge_created', { + fields: ['nostr_challenge_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), + queryInterface.addConstraint('sign_up_challenge_created', { + fields: ['app_invite_uuid'], + type: 'foreign key', + references: { + table: 'app_invite_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), ]); }); }, From 613ded0cf1b98f9cd35983643a7c4d4da4bcaf04 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 14:08:16 +0100 Subject: [PATCH 191/239] add signupchallengecompleted --- .../20250308000000-first-schema-tables.js | 28 +++++++++++++++++++ .../20250308000001-first-schema-fks.js | 20 +++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index 73cba27..c22bb65 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -344,6 +344,34 @@ module.exports = { }, { transaction: t } ), + queryInterface.createTable( + 'sign_up_challenge_completed', + { + uuid: { + type: Sequelize.UUID, + allowNull: false, + unique: true, + primaryKey: true, + }, + nostr_challenge_completed_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + app_invite_uuid: { + type: Sequelize.UUID, + allowNull: false, + }, + public_key: { + type: Sequelize.STRING, + allowNull: false, + }, + created_at: { + type: Sequelize.DATE, + allowNull: false, + }, + }, + { transaction: t } + ), ]); }); }, diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js index 573d238..c79cad7 100644 --- a/src/database/migrations/20250308000001-first-schema-fks.js +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -83,6 +83,26 @@ module.exports = { onDelete: 'cascade', onUpdate: 'cascade', }), + queryInterface.addConstraint('sign_up_challenge_completed', { + fields: ['nostr_challenge_completed_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_completed', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), + queryInterface.addConstraint('sign_up_challenge_completed', { + fields: ['app_invite_uuid'], + type: 'foreign key', + references: { + table: 'app_invite_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', + }), ]); }); }, From 9c4581d33d7f9288e0980f81b898a58953b0b9fb Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 15:37:37 +0100 Subject: [PATCH 192/239] now only active offers are shown --- src/services/offerService.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/services/offerService.js b/src/services/offerService.js index b2696aa..67884f6 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -54,21 +54,23 @@ class OfferServiceProvider { }; const getOffersByPublicKey = async (publicKey) => { - const offers = await this.models.OfferCreated.findAll({ + const activeOffers = await this.models.OfferCreated.findAll({ where: { public_key: publicKey, + '$OfferDeleted.uuid$': null, }, + include: { model: this.models.OfferDeleted, required: false }, }); - console.log(offers); + console.log(activeOffers); - if (!offers) { + if (!activeOffers) { return []; } const offersToReturn = []; - if (offers) { - for (const someOffer of offers) { + if (activeOffers) { + for (const someOffer of activeOffers) { const offerDetails = await this.models.OfferDetailsSet.findOne({ where: { offer_uuid: someOffer.uuid, From 5b35bb603df15bfa26547bade3e5eaf171eada6a Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 15:38:23 +0100 Subject: [PATCH 193/239] rename --- src/routes/apiRoutes.js | 4 +++- src/services/offerService.js | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index b93b1f0..102cf6a 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -274,7 +274,9 @@ class ApiRoutesProvider { const publicKey = req.cookies.publicKey; const offers = - await this.services.offerService.getOffersByPublicKey(publicKey); + await this.services.offerService.getActiveOffersByPublicKey( + publicKey + ); if (!offers) { return res.status(404).json({ diff --git a/src/services/offerService.js b/src/services/offerService.js index 67884f6..09c4079 100644 --- a/src/services/offerService.js +++ b/src/services/offerService.js @@ -53,7 +53,7 @@ class OfferServiceProvider { }); }; - const getOffersByPublicKey = async (publicKey) => { + const getActiveOffersByPublicKey = async (publicKey) => { const activeOffers = await this.models.OfferCreated.findAll({ where: { public_key: publicKey, @@ -102,7 +102,11 @@ class OfferServiceProvider { return offersToReturn; }; - return { createOffer, getOffersByPublicKey, deleteOffer }; + return { + createOffer, + getActiveOffersByPublicKey, + deleteOffer, + }; } } From 107edb70e731cc0721c8e19508270d036ab4dce3 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 15:51:47 +0100 Subject: [PATCH 194/239] move associations file --- src/{ => database}/associations.js | 0 src/dependencies.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{ => database}/associations.js (100%) diff --git a/src/associations.js b/src/database/associations.js similarity index 100% rename from src/associations.js rename to src/database/associations.js diff --git a/src/dependencies.js b/src/dependencies.js index a93620d..c961b30 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -10,7 +10,7 @@ function buildDependencies() { const ModelsProvider = require('./models'); const models = new ModelsProvider({ sequelize, DataTypes }).provide(); - const AssociationsDefiner = require('./associations'); + const AssociationsDefiner = require('./database/associations'); new AssociationsDefiner({ models, DataTypes }).define(); /* sequelize From 78509e657b7feadb47ce09a8d612f85d86a175d1 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 15:52:15 +0100 Subject: [PATCH 195/239] remove sync foreva --- src/dependencies.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/dependencies.js b/src/dependencies.js index c961b30..fd12f87 100644 --- a/src/dependencies.js +++ b/src/dependencies.js @@ -13,15 +13,6 @@ function buildDependencies() { const AssociationsDefiner = require('./database/associations'); new AssociationsDefiner({ models, DataTypes }).define(); - /* sequelize - .sync({ alter: true }) - .then(() => { - console.log('Database synced'); - }) - .catch((err) => { - console.error('Error syncing the database:', err); - }); - */ const ServicesProvider = require('./services'); const services = new ServicesProvider({ models, From 4f1d6b4cfbf5897f8525b743c1f34104e66cd479 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 16:04:27 +0100 Subject: [PATCH 196/239] deleting offer updates offer list --- src/public/javascript/offers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 5bcd2cc..c3746fe 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -541,7 +541,10 @@ class Offer { deleteActionText.innerText = 'Eliminar'; deleteActionArea.append(deleteActionIcon, deleteActionText); deleteActionArea.addEventListener('click', async () => { - deleteOfferByUuid(this.uuid); + await deleteOfferByUuid(this.uuid); + await myOffers.getOffersFromApi(); + await myOffers.render(); + // toggle popup }); actionButtonsArea.append(editActionArea, deleteActionArea); @@ -601,9 +604,6 @@ async function deleteOfferByUuid(offerUuid) { 'Content-Type': 'application/json', }, }); - - myOffers.getOffersFromApi(); - myOffers.render(); } buttonStartCreateOffer.addEventListener('click', () => { From 56fa3e20e7cd5e006d1bb0f21a253f73e2aa39ab Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 16:11:57 +0100 Subject: [PATCH 197/239] refactor into function --- src/public/javascript/offers.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index c3746fe..54f20c6 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -177,6 +177,12 @@ async function publishOffer() { body: JSON.stringify({ offerDetails }), }); + toggleOfferCreatedAlert(); + + toggleCreateOfferModal(); +} + +function toggleOfferCreatedAlert() { offerCreatedPopup.classList.remove('max-size-zero'); offerCreatedPopup.classList.add('revealed'); setTimeout(() => { @@ -185,8 +191,6 @@ async function publishOffer() { setTimeout(() => { offerCreatedPopup.classList.add('max-size-zero'); }, 4000); - - toggleCreateOfferModal(); } class Offer { From 7fcf62e64751f4cb2d662b4812aa8cb25c1703a3 Mon Sep 17 00:00:00 2001 From: counterweight Date: Mon, 10 Mar 2025 16:45:16 +0100 Subject: [PATCH 198/239] delete offer popup --- src/public/javascript/offers.js | 14 +++++++++++++- src/views/offers.ejs | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js index 54f20c6..f38f202 100644 --- a/src/public/javascript/offers.js +++ b/src/public/javascript/offers.js @@ -45,6 +45,7 @@ const bigNotesAcceptedCheckbox = document.getElementById( const publishOfferButton = document.getElementById('button-submit-offer'); const offerCreatedPopup = document.getElementById('offer-created-confirmation'); +const offerDeletedPopup = document.getElementById('offer-deleted-confirmation'); const ownOffersContainer = document.getElementById('own-offers-container'); @@ -193,6 +194,17 @@ function toggleOfferCreatedAlert() { }, 4000); } +function toggleOfferDeletedAlert() { + offerDeletedPopup.classList.remove('max-size-zero'); + offerDeletedPopup.classList.add('revealed'); + setTimeout(() => { + offerDeletedPopup.classList.remove('revealed'); + }, 3000); + setTimeout(() => { + offerDeletedPopup.classList.add('max-size-zero'); + }, 4000); +} + class Offer { constructor(offerData) { this.uuid = offerData.uuid; @@ -548,7 +560,7 @@ class Offer { await deleteOfferByUuid(this.uuid); await myOffers.getOffersFromApi(); await myOffers.render(); - // toggle popup + toggleOfferDeletedAlert(); }); actionButtonsArea.append(editActionArea, deleteActionArea); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 17c03b7..b04d0a1 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -201,6 +201,13 @@

¡Oferta creada! Puedes verla en tus ofertas.

+
+ +

¡Oferta eliminada!

+
<%- include("partials/appCommonScripts") %> From e81533db4c825281b216d0945eb084a02b3924a8 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 12 Mar 2025 16:28:44 +0100 Subject: [PATCH 199/239] move public --- {src/public => public}/css/createProfile.css | 0 {src/public => public}/css/invite.css | 0 {src/public => public}/css/offers.css | 0 {src/public => public}/css/seca.css | 0 .../img/bolt-lightning-black.svg | 0 .../img/bolt-lightning-gray.svg | 0 .../img/bolt-lightning-lasecagold.svg | 0 {src/public => public}/img/bolt-lightning.svg | 0 {src/public => public}/img/chains-black.svg | 0 {src/public => public}/img/chains-gray.svg | 0 .../img/chains-lasecagold.svg | 0 {src/public => public}/img/chains.svg | 0 .../img/circle-check-green.svg | 0 .../img/circle-check-white.svg | 0 .../img/circle-xmark-gray.svg | 0 {src/public => public}/img/circle-xmark.svg | 0 {src/public => public}/img/edit.svg | 0 {src/public => public}/img/envelope.svg | 0 {src/public => public}/img/eur-bill-gray.svg | 0 .../img/eur-bill-lasecagold.svg | 0 {src/public => public}/img/eur-bill.svg | 0 .../img/laseca-logo-transparent-textonly.svg | 0 .../img/laseca_logo_white.png | Bin .../img/lasecagold_ostrich.svg | 0 .../public => public}/img/many-users-gray.svg | 0 .../img/many-users-lasecagold.svg | 0 {src/public => public}/img/many-users.svg | 0 .../img/message-exclamation.svg | 0 {src/public => public}/img/phone.svg | 0 .../img/signal-messenger.svg | 0 .../public => public}/img/square-whatsapp.svg | 0 {src/public => public}/img/telegram.svg | 0 .../img/trash-can-darkred.svg | 0 .../img/trash-can-lasecagold.svg | 0 {src/public => public}/img/trash-can.svg | 0 {src/public => public}/img/user-gray.svg | 0 .../public => public}/img/user-group-gray.svg | 0 .../img/user-group-lasecagold.svg | 0 {src/public => public}/img/user-group.svg | 0 .../public => public}/img/user-lasecagold.svg | 0 {src/public => public}/img/user.svg | 0 {src/public => public}/img/white_ostrich.svg | 0 {src/public => public}/javascript/app.js | 0 .../javascript/createProfile.js | 0 {src/public => public}/javascript/home.js | 0 {src/public => public}/javascript/invite.js | 0 {src/public => public}/javascript/login.js | 0 public/javascript/offers.js | 697 ++++++++++++++++++ {src/public => public}/javascript/utils.js | 0 src/app.js | 2 +- src/public/javascript/offers.js | 685 ----------------- 51 files changed, 698 insertions(+), 686 deletions(-) rename {src/public => public}/css/createProfile.css (100%) rename {src/public => public}/css/invite.css (100%) rename {src/public => public}/css/offers.css (100%) rename {src/public => public}/css/seca.css (100%) rename {src/public => public}/img/bolt-lightning-black.svg (100%) rename {src/public => public}/img/bolt-lightning-gray.svg (100%) rename {src/public => public}/img/bolt-lightning-lasecagold.svg (100%) rename {src/public => public}/img/bolt-lightning.svg (100%) rename {src/public => public}/img/chains-black.svg (100%) rename {src/public => public}/img/chains-gray.svg (100%) rename {src/public => public}/img/chains-lasecagold.svg (100%) rename {src/public => public}/img/chains.svg (100%) rename {src/public => public}/img/circle-check-green.svg (100%) rename {src/public => public}/img/circle-check-white.svg (100%) rename {src/public => public}/img/circle-xmark-gray.svg (100%) rename {src/public => public}/img/circle-xmark.svg (100%) rename {src/public => public}/img/edit.svg (100%) rename {src/public => public}/img/envelope.svg (100%) rename {src/public => public}/img/eur-bill-gray.svg (100%) rename {src/public => public}/img/eur-bill-lasecagold.svg (100%) rename {src/public => public}/img/eur-bill.svg (100%) rename {src/public => public}/img/laseca-logo-transparent-textonly.svg (100%) rename {src/public => public}/img/laseca_logo_white.png (100%) rename {src/public => public}/img/lasecagold_ostrich.svg (100%) rename {src/public => public}/img/many-users-gray.svg (100%) rename {src/public => public}/img/many-users-lasecagold.svg (100%) rename {src/public => public}/img/many-users.svg (100%) rename {src/public => public}/img/message-exclamation.svg (100%) rename {src/public => public}/img/phone.svg (100%) rename {src/public => public}/img/signal-messenger.svg (100%) rename {src/public => public}/img/square-whatsapp.svg (100%) rename {src/public => public}/img/telegram.svg (100%) rename {src/public => public}/img/trash-can-darkred.svg (100%) rename {src/public => public}/img/trash-can-lasecagold.svg (100%) rename {src/public => public}/img/trash-can.svg (100%) rename {src/public => public}/img/user-gray.svg (100%) rename {src/public => public}/img/user-group-gray.svg (100%) rename {src/public => public}/img/user-group-lasecagold.svg (100%) rename {src/public => public}/img/user-group.svg (100%) rename {src/public => public}/img/user-lasecagold.svg (100%) rename {src/public => public}/img/user.svg (100%) rename {src/public => public}/img/white_ostrich.svg (100%) rename {src/public => public}/javascript/app.js (100%) rename {src/public => public}/javascript/createProfile.js (100%) rename {src/public => public}/javascript/home.js (100%) rename {src/public => public}/javascript/invite.js (100%) rename {src/public => public}/javascript/login.js (100%) create mode 100644 public/javascript/offers.js rename {src/public => public}/javascript/utils.js (100%) delete mode 100644 src/public/javascript/offers.js diff --git a/src/public/css/createProfile.css b/public/css/createProfile.css similarity index 100% rename from src/public/css/createProfile.css rename to public/css/createProfile.css diff --git a/src/public/css/invite.css b/public/css/invite.css similarity index 100% rename from src/public/css/invite.css rename to public/css/invite.css diff --git a/src/public/css/offers.css b/public/css/offers.css similarity index 100% rename from src/public/css/offers.css rename to public/css/offers.css diff --git a/src/public/css/seca.css b/public/css/seca.css similarity index 100% rename from src/public/css/seca.css rename to public/css/seca.css diff --git a/src/public/img/bolt-lightning-black.svg b/public/img/bolt-lightning-black.svg similarity index 100% rename from src/public/img/bolt-lightning-black.svg rename to public/img/bolt-lightning-black.svg diff --git a/src/public/img/bolt-lightning-gray.svg b/public/img/bolt-lightning-gray.svg similarity index 100% rename from src/public/img/bolt-lightning-gray.svg rename to public/img/bolt-lightning-gray.svg diff --git a/src/public/img/bolt-lightning-lasecagold.svg b/public/img/bolt-lightning-lasecagold.svg similarity index 100% rename from src/public/img/bolt-lightning-lasecagold.svg rename to public/img/bolt-lightning-lasecagold.svg diff --git a/src/public/img/bolt-lightning.svg b/public/img/bolt-lightning.svg similarity index 100% rename from src/public/img/bolt-lightning.svg rename to public/img/bolt-lightning.svg diff --git a/src/public/img/chains-black.svg b/public/img/chains-black.svg similarity index 100% rename from src/public/img/chains-black.svg rename to public/img/chains-black.svg diff --git a/src/public/img/chains-gray.svg b/public/img/chains-gray.svg similarity index 100% rename from src/public/img/chains-gray.svg rename to public/img/chains-gray.svg diff --git a/src/public/img/chains-lasecagold.svg b/public/img/chains-lasecagold.svg similarity index 100% rename from src/public/img/chains-lasecagold.svg rename to public/img/chains-lasecagold.svg diff --git a/src/public/img/chains.svg b/public/img/chains.svg similarity index 100% rename from src/public/img/chains.svg rename to public/img/chains.svg diff --git a/src/public/img/circle-check-green.svg b/public/img/circle-check-green.svg similarity index 100% rename from src/public/img/circle-check-green.svg rename to public/img/circle-check-green.svg diff --git a/src/public/img/circle-check-white.svg b/public/img/circle-check-white.svg similarity index 100% rename from src/public/img/circle-check-white.svg rename to public/img/circle-check-white.svg diff --git a/src/public/img/circle-xmark-gray.svg b/public/img/circle-xmark-gray.svg similarity index 100% rename from src/public/img/circle-xmark-gray.svg rename to public/img/circle-xmark-gray.svg diff --git a/src/public/img/circle-xmark.svg b/public/img/circle-xmark.svg similarity index 100% rename from src/public/img/circle-xmark.svg rename to public/img/circle-xmark.svg diff --git a/src/public/img/edit.svg b/public/img/edit.svg similarity index 100% rename from src/public/img/edit.svg rename to public/img/edit.svg diff --git a/src/public/img/envelope.svg b/public/img/envelope.svg similarity index 100% rename from src/public/img/envelope.svg rename to public/img/envelope.svg diff --git a/src/public/img/eur-bill-gray.svg b/public/img/eur-bill-gray.svg similarity index 100% rename from src/public/img/eur-bill-gray.svg rename to public/img/eur-bill-gray.svg diff --git a/src/public/img/eur-bill-lasecagold.svg b/public/img/eur-bill-lasecagold.svg similarity index 100% rename from src/public/img/eur-bill-lasecagold.svg rename to public/img/eur-bill-lasecagold.svg diff --git a/src/public/img/eur-bill.svg b/public/img/eur-bill.svg similarity index 100% rename from src/public/img/eur-bill.svg rename to public/img/eur-bill.svg diff --git a/src/public/img/laseca-logo-transparent-textonly.svg b/public/img/laseca-logo-transparent-textonly.svg similarity index 100% rename from src/public/img/laseca-logo-transparent-textonly.svg rename to public/img/laseca-logo-transparent-textonly.svg diff --git a/src/public/img/laseca_logo_white.png b/public/img/laseca_logo_white.png similarity index 100% rename from src/public/img/laseca_logo_white.png rename to public/img/laseca_logo_white.png diff --git a/src/public/img/lasecagold_ostrich.svg b/public/img/lasecagold_ostrich.svg similarity index 100% rename from src/public/img/lasecagold_ostrich.svg rename to public/img/lasecagold_ostrich.svg diff --git a/src/public/img/many-users-gray.svg b/public/img/many-users-gray.svg similarity index 100% rename from src/public/img/many-users-gray.svg rename to public/img/many-users-gray.svg diff --git a/src/public/img/many-users-lasecagold.svg b/public/img/many-users-lasecagold.svg similarity index 100% rename from src/public/img/many-users-lasecagold.svg rename to public/img/many-users-lasecagold.svg diff --git a/src/public/img/many-users.svg b/public/img/many-users.svg similarity index 100% rename from src/public/img/many-users.svg rename to public/img/many-users.svg diff --git a/src/public/img/message-exclamation.svg b/public/img/message-exclamation.svg similarity index 100% rename from src/public/img/message-exclamation.svg rename to public/img/message-exclamation.svg diff --git a/src/public/img/phone.svg b/public/img/phone.svg similarity index 100% rename from src/public/img/phone.svg rename to public/img/phone.svg diff --git a/src/public/img/signal-messenger.svg b/public/img/signal-messenger.svg similarity index 100% rename from src/public/img/signal-messenger.svg rename to public/img/signal-messenger.svg diff --git a/src/public/img/square-whatsapp.svg b/public/img/square-whatsapp.svg similarity index 100% rename from src/public/img/square-whatsapp.svg rename to public/img/square-whatsapp.svg diff --git a/src/public/img/telegram.svg b/public/img/telegram.svg similarity index 100% rename from src/public/img/telegram.svg rename to public/img/telegram.svg diff --git a/src/public/img/trash-can-darkred.svg b/public/img/trash-can-darkred.svg similarity index 100% rename from src/public/img/trash-can-darkred.svg rename to public/img/trash-can-darkred.svg diff --git a/src/public/img/trash-can-lasecagold.svg b/public/img/trash-can-lasecagold.svg similarity index 100% rename from src/public/img/trash-can-lasecagold.svg rename to public/img/trash-can-lasecagold.svg diff --git a/src/public/img/trash-can.svg b/public/img/trash-can.svg similarity index 100% rename from src/public/img/trash-can.svg rename to public/img/trash-can.svg diff --git a/src/public/img/user-gray.svg b/public/img/user-gray.svg similarity index 100% rename from src/public/img/user-gray.svg rename to public/img/user-gray.svg diff --git a/src/public/img/user-group-gray.svg b/public/img/user-group-gray.svg similarity index 100% rename from src/public/img/user-group-gray.svg rename to public/img/user-group-gray.svg diff --git a/src/public/img/user-group-lasecagold.svg b/public/img/user-group-lasecagold.svg similarity index 100% rename from src/public/img/user-group-lasecagold.svg rename to public/img/user-group-lasecagold.svg diff --git a/src/public/img/user-group.svg b/public/img/user-group.svg similarity index 100% rename from src/public/img/user-group.svg rename to public/img/user-group.svg diff --git a/src/public/img/user-lasecagold.svg b/public/img/user-lasecagold.svg similarity index 100% rename from src/public/img/user-lasecagold.svg rename to public/img/user-lasecagold.svg diff --git a/src/public/img/user.svg b/public/img/user.svg similarity index 100% rename from src/public/img/user.svg rename to public/img/user.svg diff --git a/src/public/img/white_ostrich.svg b/public/img/white_ostrich.svg similarity index 100% rename from src/public/img/white_ostrich.svg rename to public/img/white_ostrich.svg diff --git a/src/public/javascript/app.js b/public/javascript/app.js similarity index 100% rename from src/public/javascript/app.js rename to public/javascript/app.js diff --git a/src/public/javascript/createProfile.js b/public/javascript/createProfile.js similarity index 100% rename from src/public/javascript/createProfile.js rename to public/javascript/createProfile.js diff --git a/src/public/javascript/home.js b/public/javascript/home.js similarity index 100% rename from src/public/javascript/home.js rename to public/javascript/home.js diff --git a/src/public/javascript/invite.js b/public/javascript/invite.js similarity index 100% rename from src/public/javascript/invite.js rename to public/javascript/invite.js diff --git a/src/public/javascript/login.js b/public/javascript/login.js similarity index 100% rename from src/public/javascript/login.js rename to public/javascript/login.js diff --git a/public/javascript/offers.js b/public/javascript/offers.js new file mode 100644 index 0000000..4e57b19 --- /dev/null +++ b/public/javascript/offers.js @@ -0,0 +1,697 @@ +function offersPage() { + const buttonStartCreateOffer = document.getElementById( + 'button-start-create-offer' + ); + const buttonViewMyOffers = document.getElementById('button-view-my-offers'); + const closeOffer = document.getElementById('close-offer'); + const createOfferModalRoot = document.getElementById( + 'create-offer-modal-root' + ); + const viewMyOffersRoot = document.getElementById('view-my-offers-root'); + const buyOrSellButtonGroup = document.getElementById( + 'button-group-buy-or-sell' + ); + const buyOrSellButtons = buyOrSellButtonGroup.querySelectorAll('button'); + const buyButton = document.getElementById('button-buy-bitcoin'); + const sellButton = document.getElementById('button-sell-bitcoin'); + + const premiumValue = document.getElementById('premium-value'); + const buttonIncreasePremium = document.getElementById( + 'button-increase-premium' + ); + + const buttonDecreasePremium = document.getElementById( + 'button-decrease-premium' + ); + + const eurAmountInput = document.getElementById('input-eur-amount'); + const btcAmountInput = document.getElementById('input-btc-amount'); + + const placeInput = document.getElementById('place-input'); + const timeInput = document.getElementById('time-input'); + + const onchainCheckbox = document.getElementById('onchain-checkbox'); + const lightningCheckbox = document.getElementById('lightning-checkbox'); + + const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; + + const myTrustedCheckbox = document.getElementById('my-trusted-checkbox'); + const myTrustedTrustedCheckbox = document.getElementById( + 'my-trusted-trusted-checkbox' + ); + const allMembersCheckbox = document.getElementById('all-members-checkbox'); + + const bigNotesAcceptedCheckbox = document.getElementById( + 'large-bills-checkbox' + ); + + const publishOfferButton = document.getElementById('button-submit-offer'); + + const offerCreatedPopup = document.getElementById( + 'offer-created-confirmation' + ); + const offerDeletedPopup = document.getElementById( + 'offer-deleted-confirmation' + ); + + const ownOffersContainer = document.getElementById('own-offers-container'); + + function toggleCreateOfferModal() { + createOfferModalRoot.classList.toggle('shown'); + } + + function toggleViewMyOffersPanel() { + viewMyOffersRoot.style.display = + viewMyOffersRoot.style.display === 'block' ? 'none' : 'block'; + } + + function modifyPremiumValue(delta) { + const regexExpression = /-*\d+/; + const numValue = parseInt(premiumValue.innerText.match(regexExpression)[0]); + + const newValue = `${numValue + delta}%`; + + premiumValue.innerText = newValue; + } + + function toggleBuyOrSellButtonGroup() { + buyOrSellButtons.forEach((button) => { + if (button.classList.contains('selected')) { + button.classList.remove('selected'); + } else { + button.classList.add('selected'); + } + }); + } + + function readIntFromEurAmountInput() { + const eurAmountFieldValue = eurAmountInput.value; + const regularExpression = /([\d\s]+)/; + const matchResult = eurAmountFieldValue.match(regularExpression); + + if (!matchResult) { + return null; + } + + const numberString = matchResult[1]; + const cleanInputNumber = parseInt(numberString.replace(/\s/gi, '')); + + return cleanInputNumber; + } + + function validateAndFormatEurAmountInput() { + const cleanInputNumber = readIntFromEurAmountInput(); + eurAmountInput.classList.remove('input-is-valid', 'input-is-invalid'); + if (cleanInputNumber) { + eurAmountInput.value = formatNumberWithSpaces(cleanInputNumber); + eurAmountInput.classList.add('input-is-valid'); + return; + } + + eurAmountInput.classList.add('input-is-invalid'); + } + + function updateBtcInput() { + const eurToSatRate = 1021; + const cleanEurAmount = readIntFromEurAmountInput(); + + const satsAmount = cleanEurAmount * eurToSatRate; + const formattedSatsAmount = formatNumberWithSpaces(satsAmount); + btcAmountInput.value = formattedSatsAmount; + } + + function validateBitcoinMethodCheckboxes(clickedCheckbox) { + let checkedCount = btcMethodCheckboxes.filter((cb) => cb.checked).length; + if (checkedCount === 0) { + clickedCheckbox.checked = true; + } + } + + function applyTrustCheckboxConstraints(pressedCheckbox) { + if (pressedCheckbox === myTrustedTrustedCheckbox) { + console.log('first case!'); + if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { + allMembersCheckbox.checked = false; + } + } + + if (pressedCheckbox === allMembersCheckbox) { + console.log('second case!'); + if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { + myTrustedTrustedCheckbox.checked = true; + } + } + } + + async function publishOffer() { + let wants; + if (buyButton.classList.contains('selected')) { + wants = 'BTC'; + } + if (sellButton.classList.contains('selected')) { + wants = 'EUR'; + } + + const premium = parseInt(premiumValue.innerText.match(/\d+/)[0]) / 100; + const trade_amount_eur = eurAmountInput.value; + const location_details = placeInput.value; + const time_availability_details = timeInput.value; + const is_onchain_accepted = onchainCheckbox.checked; + const is_lightning_accepted = lightningCheckbox.checked; + const show_offer_to_trusted = myTrustedCheckbox.checked; + const show_offer_to_trusted_trusted = myTrustedTrustedCheckbox.checked; + const show_offer_to_all_members = allMembersCheckbox.checked; + const are_big_notes_accepted = bigNotesAcceptedCheckbox.checked; + + const offerDetails = { + wants, + premium, + trade_amount_eur, + location_details, + time_availability_details, + is_onchain_accepted, + is_lightning_accepted, + show_offer_to_trusted, + show_offer_to_trusted_trusted, + show_offer_to_all_members, + are_big_notes_accepted, + }; + + await fetch('/api/offer', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ offerDetails }), + }); + + toggleOfferCreatedAlert(); + + toggleCreateOfferModal(); + } + + function toggleOfferCreatedAlert() { + offerCreatedPopup.classList.remove('max-size-zero'); + offerCreatedPopup.classList.add('revealed'); + setTimeout(() => { + offerCreatedPopup.classList.remove('revealed'); + }, 3000); + setTimeout(() => { + offerCreatedPopup.classList.add('max-size-zero'); + }, 4000); + } + + function toggleOfferDeletedAlert() { + offerDeletedPopup.classList.remove('max-size-zero'); + offerDeletedPopup.classList.add('revealed'); + setTimeout(() => { + offerDeletedPopup.classList.remove('revealed'); + }, 3000); + setTimeout(() => { + offerDeletedPopup.classList.add('max-size-zero'); + }, 4000); + } + + class Offer { + constructor(offerData) { + this.uuid = offerData.uuid; + this.public_key = offerData.public_key; + this.wants = offerData.wants; + this.premium = offerData.premium; + this.trade_amount_eur = offerData.trade_amount_eur; + this.location_details = offerData.location_details; + this.time_availability_details = offerData.time_availability_details; + this.show_offer_to_trusted = offerData.show_offer_to_trusted; + this.show_offer_to_trusted_trusted = + offerData.show_offer_to_trusted_trusted; + this.show_offer_to_all_members = offerData.show_offer_to_all_members; + this.is_onchain_accepted = offerData.is_onchain_accepted; + this.is_lightning_accepted = offerData.is_lightning_accepted; + this.are_big_notes_accepted = offerData.are_big_notes_accepted; + this.created_at = offerData.created_at; + this.last_updated_at = offerData.last_updated_at; + } + + buildHTML() { + const offerCard = document.createElement('div'); + offerCard.classList.add('myoffer-card'); + offerCard.classList.add('shadowed-round-area'); + + const tradeDescDiv = document.createElement('div'); + tradeDescDiv.classList.add('trade-desc'); + + const youBuyText = document.createElement('p'); + youBuyText.classList.add('offer-card-content-title'); + youBuyText.innerText = 'Compras'; + tradeDescDiv.append(youBuyText); + + const youBuyData = document.createElement('p'); + youBuyData.classList.add('offer-card-content-data'); + if (this.wants === 'BTC') { + youBuyData.innerText = `${this.trade_amount_eur * 1021} sats`; + } + if (this.wants === 'EUR') { + youBuyData.innerText = `${this.trade_amount_eur} €`; + } + tradeDescDiv.append(youBuyData); + + const youSellText = document.createElement('p'); + youSellText.id = 'you-sell-title'; + youSellText.classList.add('offer-card-content-title'); + youSellText.innerText = 'Vendes'; + tradeDescDiv.append(youSellText); + + const youSellData = document.createElement('p'); + youSellData.classList.add('offer-card-content-data'); + if (this.wants === 'BTC') { + youSellData.innerText = `${this.trade_amount_eur} €`; + } + if (this.wants === 'EUR') { + youSellData.innerText = `${this.trade_amount_eur * 1021} sats`; + } + tradeDescDiv.append(youSellData); + + const premiumDescDiv = document.createElement('div'); + premiumDescDiv.classList.add('premium-desc'); + + const premiumTitle = document.createElement('p'); + premiumTitle.classList.add('offer-card-content-title'); + premiumTitle.innerText = 'Premium'; + premiumDescDiv.append(premiumTitle); + + const premiumData = document.createElement('p'); + premiumData.classList.add('offer-card-content-data'); + premiumData.innerText = `${this.premium * 100} %`; + premiumDescDiv.append(premiumData); + + const offerPriceTitle = document.createElement('p'); + offerPriceTitle.classList.add('offer-card-content-title'); + offerPriceTitle.innerText = 'Precio oferta'; + premiumDescDiv.append(offerPriceTitle); + + const offerPriceData = document.createElement('p'); + offerPriceData.classList.add('offer-card-content-data'); + offerPriceData.innerText = `90000 €/BTC`; + premiumDescDiv.append(offerPriceData); + + const marketPriceTitle = document.createElement('p'); + marketPriceTitle.classList.add('offer-card-content-title'); + marketPriceTitle.innerText = 'Precio mercado'; + premiumDescDiv.append(marketPriceTitle); + + const marketPriceData = document.createElement('p'); + marketPriceData.innerText = `88000 €/BTC`; + premiumDescDiv.append(marketPriceData); + + const whereDescDiv = document.createElement('div'); + whereDescDiv.classList.add('where-desc'); + + const whereDescTitle = document.createElement('p'); + whereDescTitle.classList.add('offer-card-content-title'); + whereDescTitle.innerText = 'Dónde'; + whereDescDiv.append(whereDescTitle); + + const whereDescData = document.createElement('p'); + whereDescData.classList.add('offer-long-text'); + whereDescData.innerText = `${this.location_details}`; + whereDescDiv.append(whereDescData); + + const whenDescDiv = document.createElement('div'); + whenDescDiv.classList.add('when-desc'); + + const whenDescTitle = document.createElement('p'); + whenDescTitle.classList.add('offer-card-content-title'); + whenDescTitle.innerText = 'Cúando'; + whenDescDiv.append(whenDescTitle); + + const whenDescData = document.createElement('p'); + whenDescData.classList.add('offer-long-text'); + whenDescData.innerText = `${this.time_availability_details}`; + whenDescDiv.append(whenDescData); + + const bitcoinMethodsDiv = document.createElement('div'); + bitcoinMethodsDiv.classList.add('bitcoin-methods-desc'); + + const bitcoinMethodsTitle = document.createElement('p'); + bitcoinMethodsTitle.classList.add('offer-card-content-title'); + bitcoinMethodsTitle.innerText = 'Protocolos Bitcoin aceptados'; + bitcoinMethodsDiv.append(bitcoinMethodsTitle); + + const onchainAcceptedContainer = document.createElement('div'); + onchainAcceptedContainer.classList.add('left-icon-checkboxed-field'); + if (this.is_onchain_accepted) { + const onchainIcon = document.createElement('img'); + onchainIcon.src = '/img/chains-lasecagold.svg'; + const onchainText = document.createElement('p'); + onchainText.innerText = 'Onchain'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-check-green.svg'; + + onchainAcceptedContainer.append(onchainIcon, onchainText, checkIcon); + } else { + const onchainIcon = document.createElement('img'); + onchainIcon.src = '/img/chains-gray.svg'; + const onchainText = document.createElement('p'); + onchainText.innerText = 'Onchain'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-xmark-gray.svg'; + + onchainAcceptedContainer.append(onchainIcon, onchainText, checkIcon); + } + const lightningAcceptedContainer = document.createElement('div'); + + lightningAcceptedContainer.classList.add('left-icon-checkboxed-field'); + if (this.is_lightning_accepted) { + const lightningIcon = document.createElement('img'); + lightningIcon.src = '/img/bolt-lightning-lasecagold.svg'; + const lightningText = document.createElement('p'); + lightningText.innerText = 'Lightning'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-check-green.svg'; + + lightningAcceptedContainer.append( + lightningIcon, + lightningText, + checkIcon + ); + } else { + const lightningIcon = document.createElement('img'); + lightningIcon.src = '/img/bolt-lightning-gray.svg'; + const lightningText = document.createElement('p'); + lightningText.innerText = 'Lightning'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-xmark-gray.svg'; + + lightningAcceptedContainer.append( + lightningIcon, + lightningText, + checkIcon + ); + } + + bitcoinMethodsDiv.append( + onchainAcceptedContainer, + lightningAcceptedContainer + ); + + const visibilityDiv = document.createElement('div'); + visibilityDiv.classList.add('visibility-desc'); + + const visibilityTitle = document.createElement('p'); + visibilityTitle.classList.add('offer-card-content-title'); + visibilityTitle.innerText = 'Visibilidad'; + visibilityDiv.append(visibilityTitle); + + const showOfferToTrustedContainer = document.createElement('div'); + showOfferToTrustedContainer.classList.add('right-icon-checkboxed-field'); + + if (this.show_offer_to_trusted) { + const showOfferToTrustedIcon = document.createElement('img'); + showOfferToTrustedIcon.src = '/img/user-lasecagold.svg'; + const showOfferToTrustedText = document.createElement('p'); + showOfferToTrustedText.innerText = 'Confiados'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-check-green.svg'; + + showOfferToTrustedContainer.append( + showOfferToTrustedIcon, + showOfferToTrustedText, + checkIcon + ); + } else { + const showOfferToTrustedIcon = document.createElement('img'); + showOfferToTrustedIcon.src = '/img/user-gray.svg'; + const showOfferToTrustedText = document.createElement('p'); + showOfferToTrustedText.innerText = 'Confiados'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-xmark-gray.svg'; + + showOfferToTrustedContainer.append( + showOfferToTrustedIcon, + showOfferToTrustedText, + checkIcon + ); + } + + const showOfferToTrustedTrustedContainer = document.createElement('div'); + showOfferToTrustedTrustedContainer.classList.add( + 'right-icon-checkboxed-field' + ); + + if (this.show_offer_to_trusted_trusted) { + const showOfferToTrustedTrustedIcon = document.createElement('img'); + showOfferToTrustedTrustedIcon.src = '/img/user-group-lasecagold.svg'; + const showOfferToTrustedTrustedText = document.createElement('p'); + showOfferToTrustedTrustedText.innerText = 'Sus confiados'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-check-green.svg'; + + showOfferToTrustedTrustedContainer.append( + showOfferToTrustedTrustedIcon, + showOfferToTrustedTrustedText, + checkIcon + ); + } else { + const showOfferToTrustedTrustedIcon = document.createElement('img'); + showOfferToTrustedTrustedIcon.src = '/img/user-group-gray.svg'; + const showOfferToTrustedTrustedText = document.createElement('p'); + showOfferToTrustedTrustedText.innerText = 'Sus confiados'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-xmark-gray.svg'; + + showOfferToTrustedTrustedContainer.append( + showOfferToTrustedTrustedIcon, + showOfferToTrustedTrustedText, + checkIcon + ); + } + + const showOfferToAllMembersContainer = document.createElement('div'); + showOfferToAllMembersContainer.classList.add( + 'right-icon-checkboxed-field' + ); + + if (this.show_offer_to_all_members) { + const showOfferToAllMembersIcon = document.createElement('img'); + showOfferToAllMembersIcon.src = '/img/many-users-lasecagold.svg'; + const showOfferToAllMembersText = document.createElement('p'); + showOfferToAllMembersText.innerText = 'Todos'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-check-green.svg'; + + showOfferToAllMembersContainer.append( + showOfferToAllMembersIcon, + showOfferToAllMembersText, + checkIcon + ); + } else { + const showOfferToAllMembersIcon = document.createElement('img'); + showOfferToAllMembersIcon.src = '/img/many-users-gray.svg'; + const showOfferToAllMembersText = document.createElement('p'); + showOfferToAllMembersText.innerText = 'Todos'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-xmark-gray.svg'; + + showOfferToAllMembersContainer.append( + showOfferToAllMembersIcon, + showOfferToAllMembersText, + checkIcon + ); + } + visibilityDiv.append( + showOfferToTrustedContainer, + showOfferToTrustedTrustedContainer, + showOfferToAllMembersContainer + ); + + const otherOfferFeaturesDiv = document.createElement('div'); + otherOfferFeaturesDiv.classList.add('other-desc'); + + const otherOfferFeaturesTitle = document.createElement('p'); + otherOfferFeaturesTitle.classList.add('offer-card-content-title'); + otherOfferFeaturesTitle.innerText = 'Otros'; + otherOfferFeaturesDiv.append(otherOfferFeaturesTitle); + + const areBigNotesAcceptedContainer = document.createElement('div'); + areBigNotesAcceptedContainer.classList.add('left-icon-checkboxed-field'); + + if (this.are_big_notes_accepted) { + const areBigNotesAcceptedIcon = document.createElement('img'); + areBigNotesAcceptedIcon.src = '/img/eur-bill-lasecagold.svg'; + const areBigNotesAcceptedText = document.createElement('p'); + areBigNotesAcceptedText.innerText = 'Billetes grandes'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-check-green.svg'; + + areBigNotesAcceptedContainer.append( + areBigNotesAcceptedIcon, + areBigNotesAcceptedText, + checkIcon + ); + } else { + const areBigNotesAcceptedIcon = document.createElement('img'); + areBigNotesAcceptedIcon.src = '/img/eur-bill-gray.svg'; + const areBigNotesAcceptedText = document.createElement('p'); + areBigNotesAcceptedText.innerText = 'Billetes grandes'; + const checkIcon = document.createElement('img'); + checkIcon.src = '/img/circle-xmark-gray.svg'; + + areBigNotesAcceptedContainer.append( + areBigNotesAcceptedIcon, + areBigNotesAcceptedText, + checkIcon + ); + } + + otherOfferFeaturesDiv.append(areBigNotesAcceptedContainer); + + const actionButtonsArea = document.createElement('p'); + actionButtonsArea.classList.add('offer-action-buttons-area'); + + const editActionArea = document.createElement('div'); + editActionArea.classList.add('offer-action-area'); + editActionArea.classList.add('subtle-box'); + const editActionIcon = document.createElement('img'); + editActionIcon.src = '/img/edit.svg'; + const editActionText = document.createElement('p'); + editActionText.innerText = 'Editar'; + editActionArea.append(editActionIcon, editActionText); + + const deleteActionArea = document.createElement('div'); + deleteActionArea.classList.add('offer-action-area'); + deleteActionArea.classList.add('subtle-box'); + const deleteActionIcon = document.createElement('img'); + deleteActionIcon.src = '/img/trash-can-darkred.svg'; + const deleteActionText = document.createElement('p'); + deleteActionText.innerText = 'Eliminar'; + deleteActionArea.append(deleteActionIcon, deleteActionText); + deleteActionArea.addEventListener('click', async () => { + await deleteOfferByUuid(this.uuid); + await myOffers.getOffersFromApi(); + await myOffers.render(); + toggleOfferDeletedAlert(); + }); + + actionButtonsArea.append(editActionArea, deleteActionArea); + + offerCard.append( + tradeDescDiv, + premiumDescDiv, + whereDescDiv, + whenDescDiv, + bitcoinMethodsDiv, + visibilityDiv, + otherOfferFeaturesDiv, + actionButtonsArea + ); + + return offerCard; + } + } + + class MyOffers { + constructor(ownOffersContainerElement) { + this.ownOffersContainerElement = ownOffersContainerElement; + this.offers = []; + } + + async getOffersFromApi() { + const offersResponse = await fetch('/api/publickey-offers'); + + this.offers = []; + + const offersData = (await offersResponse.json()).data; + if (offersResponse.ok) { + for (const record of offersData) { + this.offers.push(new Offer(record)); + } + } + } + + async render() { + if (this.offers.length === 0) { + this.ownOffersContainerElement.innerHTML = + '

Vaya, no hay nada por aquí...

'; + return; + } + this.ownOffersContainerElement.innerHTML = ''; + + for (const someOffer of this.offers) { + this.ownOffersContainerElement.append(someOffer.buildHTML()); + } + } + } + + async function deleteOfferByUuid(offerUuid) { + await fetch(`/api/offer/${offerUuid}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + buttonStartCreateOffer.addEventListener('click', () => { + toggleCreateOfferModal(); + }); + + buttonViewMyOffers.addEventListener('click', async () => { + await myOffers.getOffersFromApi(); + await myOffers.render(); + toggleViewMyOffersPanel(); + }); + + closeOffer.addEventListener('click', () => { + toggleCreateOfferModal(); + }); + + buyOrSellButtons.forEach((button) => { + button.addEventListener('click', () => { + toggleBuyOrSellButtonGroup(); + }); + }); + + buttonIncreasePremium.addEventListener('click', () => { + modifyPremiumValue(1); + }); + + buttonDecreasePremium.addEventListener('click', () => { + modifyPremiumValue(-1); + }); + + eurAmountInput.addEventListener('blur', () => { + validateAndFormatEurAmountInput(); + updateBtcInput(); + }); + + eurAmountInput.addEventListener('input', () => { + eurAmountInput.value = eurAmountInput.value.replace(/[^0-9]/g, ''); + updateBtcInput(); + }); + + for (const btcMethodCheckbox of btcMethodCheckboxes) { + btcMethodCheckbox.addEventListener('click', () => { + validateBitcoinMethodCheckboxes(btcMethodCheckbox); + }); + } + + myTrustedTrustedCheckbox.addEventListener('click', () => { + applyTrustCheckboxConstraints(myTrustedTrustedCheckbox); + }); + + allMembersCheckbox.addEventListener('click', () => { + applyTrustCheckboxConstraints(allMembersCheckbox); + }); + + publishOfferButton.addEventListener('click', async () => { + await publishOffer(); + await myOffers.getOffersFromApi(); + await myOffers.render(); + }); + + updateBtcInput(); + + const myOffers = new MyOffers(ownOffersContainer); +} + +offersPage; diff --git a/src/public/javascript/utils.js b/public/javascript/utils.js similarity index 100% rename from src/public/javascript/utils.js rename to public/javascript/utils.js diff --git a/src/app.js b/src/app.js index dc3cf83..76a3146 100644 --- a/src/app.js +++ b/src/app.js @@ -21,7 +21,7 @@ function createApp(dependencies) { app.use('/', dependencies.webRoutes); app.use('/api', dependencies.apiRoutes); - app.use(express.static(path.join(__dirname, 'public'))); + app.use(express.static(path.join(__dirname, '../public'))); app.disable('etag'); //avoids 304 responses diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js deleted file mode 100644 index f38f202..0000000 --- a/src/public/javascript/offers.js +++ /dev/null @@ -1,685 +0,0 @@ -const buttonStartCreateOffer = document.getElementById( - 'button-start-create-offer' -); -const buttonViewMyOffers = document.getElementById('button-view-my-offers'); -const closeOffer = document.getElementById('close-offer'); -const createOfferModalRoot = document.getElementById('create-offer-modal-root'); -const viewMyOffersRoot = document.getElementById('view-my-offers-root'); -const buyOrSellButtonGroup = document.getElementById( - 'button-group-buy-or-sell' -); -const buyOrSellButtons = buyOrSellButtonGroup.querySelectorAll('button'); -const buyButton = document.getElementById('button-buy-bitcoin'); -const sellButton = document.getElementById('button-sell-bitcoin'); - -const premiumValue = document.getElementById('premium-value'); -const buttonIncreasePremium = document.getElementById( - 'button-increase-premium' -); - -const buttonDecreasePremium = document.getElementById( - 'button-decrease-premium' -); - -const eurAmountInput = document.getElementById('input-eur-amount'); -const btcAmountInput = document.getElementById('input-btc-amount'); - -const placeInput = document.getElementById('place-input'); -const timeInput = document.getElementById('time-input'); - -const onchainCheckbox = document.getElementById('onchain-checkbox'); -const lightningCheckbox = document.getElementById('lightning-checkbox'); - -const btcMethodCheckboxes = [onchainCheckbox, lightningCheckbox]; - -const myTrustedCheckbox = document.getElementById('my-trusted-checkbox'); -const myTrustedTrustedCheckbox = document.getElementById( - 'my-trusted-trusted-checkbox' -); -const allMembersCheckbox = document.getElementById('all-members-checkbox'); - -const bigNotesAcceptedCheckbox = document.getElementById( - 'large-bills-checkbox' -); - -const publishOfferButton = document.getElementById('button-submit-offer'); - -const offerCreatedPopup = document.getElementById('offer-created-confirmation'); -const offerDeletedPopup = document.getElementById('offer-deleted-confirmation'); - -const ownOffersContainer = document.getElementById('own-offers-container'); - -function toggleCreateOfferModal() { - createOfferModalRoot.classList.toggle('shown'); -} - -function toggleViewMyOffersPanel() { - viewMyOffersRoot.style.display = - viewMyOffersRoot.style.display === 'block' ? 'none' : 'block'; -} - -function modifyPremiumValue(delta) { - const regexExpression = /-*\d+/; - const numValue = parseInt(premiumValue.innerText.match(regexExpression)[0]); - - const newValue = `${numValue + delta}%`; - - premiumValue.innerText = newValue; -} - -function toggleBuyOrSellButtonGroup() { - buyOrSellButtons.forEach((button) => { - if (button.classList.contains('selected')) { - button.classList.remove('selected'); - } else { - button.classList.add('selected'); - } - }); -} - -function readIntFromEurAmountInput() { - const eurAmountFieldValue = eurAmountInput.value; - const regularExpression = /([\d\s]+)/; - const matchResult = eurAmountFieldValue.match(regularExpression); - - if (!matchResult) { - return null; - } - - const numberString = matchResult[1]; - const cleanInputNumber = parseInt(numberString.replace(/\s/gi, '')); - - return cleanInputNumber; -} - -function validateAndFormatEurAmountInput() { - const cleanInputNumber = readIntFromEurAmountInput(); - eurAmountInput.classList.remove('input-is-valid', 'input-is-invalid'); - if (cleanInputNumber) { - eurAmountInput.value = formatNumberWithSpaces(cleanInputNumber); - eurAmountInput.classList.add('input-is-valid'); - return; - } - - eurAmountInput.classList.add('input-is-invalid'); -} - -function updateBtcInput() { - const eurToSatRate = 1021; - const cleanEurAmount = readIntFromEurAmountInput(); - - const satsAmount = cleanEurAmount * eurToSatRate; - const formattedSatsAmount = formatNumberWithSpaces(satsAmount); - btcAmountInput.value = formattedSatsAmount; -} - -function validateBitcoinMethodCheckboxes(clickedCheckbox) { - let checkedCount = btcMethodCheckboxes.filter((cb) => cb.checked).length; - if (checkedCount === 0) { - clickedCheckbox.checked = true; - } -} - -function applyTrustCheckboxConstraints(pressedCheckbox) { - if (pressedCheckbox === myTrustedTrustedCheckbox) { - console.log('first case!'); - if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { - allMembersCheckbox.checked = false; - } - } - - if (pressedCheckbox === allMembersCheckbox) { - console.log('second case!'); - if (!myTrustedTrustedCheckbox.checked && allMembersCheckbox.checked) { - myTrustedTrustedCheckbox.checked = true; - } - } -} - -async function publishOffer() { - let wants; - if (buyButton.classList.contains('selected')) { - wants = 'BTC'; - } - if (sellButton.classList.contains('selected')) { - wants = 'EUR'; - } - - const premium = parseInt(premiumValue.innerText.match(/\d+/)[0]) / 100; - const trade_amount_eur = eurAmountInput.value; - const location_details = placeInput.value; - const time_availability_details = timeInput.value; - const is_onchain_accepted = onchainCheckbox.checked; - const is_lightning_accepted = lightningCheckbox.checked; - const show_offer_to_trusted = myTrustedCheckbox.checked; - const show_offer_to_trusted_trusted = myTrustedTrustedCheckbox.checked; - const show_offer_to_all_members = allMembersCheckbox.checked; - const are_big_notes_accepted = bigNotesAcceptedCheckbox.checked; - - const offerDetails = { - wants, - premium, - trade_amount_eur, - location_details, - time_availability_details, - is_onchain_accepted, - is_lightning_accepted, - show_offer_to_trusted, - show_offer_to_trusted_trusted, - show_offer_to_all_members, - are_big_notes_accepted, - }; - - await fetch('/api/offer', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ offerDetails }), - }); - - toggleOfferCreatedAlert(); - - toggleCreateOfferModal(); -} - -function toggleOfferCreatedAlert() { - offerCreatedPopup.classList.remove('max-size-zero'); - offerCreatedPopup.classList.add('revealed'); - setTimeout(() => { - offerCreatedPopup.classList.remove('revealed'); - }, 3000); - setTimeout(() => { - offerCreatedPopup.classList.add('max-size-zero'); - }, 4000); -} - -function toggleOfferDeletedAlert() { - offerDeletedPopup.classList.remove('max-size-zero'); - offerDeletedPopup.classList.add('revealed'); - setTimeout(() => { - offerDeletedPopup.classList.remove('revealed'); - }, 3000); - setTimeout(() => { - offerDeletedPopup.classList.add('max-size-zero'); - }, 4000); -} - -class Offer { - constructor(offerData) { - this.uuid = offerData.uuid; - this.public_key = offerData.public_key; - this.wants = offerData.wants; - this.premium = offerData.premium; - this.trade_amount_eur = offerData.trade_amount_eur; - this.location_details = offerData.location_details; - this.time_availability_details = offerData.time_availability_details; - this.show_offer_to_trusted = offerData.show_offer_to_trusted; - this.show_offer_to_trusted_trusted = - offerData.show_offer_to_trusted_trusted; - this.show_offer_to_all_members = offerData.show_offer_to_all_members; - this.is_onchain_accepted = offerData.is_onchain_accepted; - this.is_lightning_accepted = offerData.is_lightning_accepted; - this.are_big_notes_accepted = offerData.are_big_notes_accepted; - this.created_at = offerData.created_at; - this.last_updated_at = offerData.last_updated_at; - } - - buildHTML() { - const offerCard = document.createElement('div'); - offerCard.classList.add('myoffer-card'); - offerCard.classList.add('shadowed-round-area'); - - const tradeDescDiv = document.createElement('div'); - tradeDescDiv.classList.add('trade-desc'); - - const youBuyText = document.createElement('p'); - youBuyText.classList.add('offer-card-content-title'); - youBuyText.innerText = 'Compras'; - tradeDescDiv.append(youBuyText); - - const youBuyData = document.createElement('p'); - youBuyData.classList.add('offer-card-content-data'); - if (this.wants === 'BTC') { - youBuyData.innerText = `${this.trade_amount_eur * 1021} sats`; - } - if (this.wants === 'EUR') { - youBuyData.innerText = `${this.trade_amount_eur} €`; - } - tradeDescDiv.append(youBuyData); - - const youSellText = document.createElement('p'); - youSellText.id = 'you-sell-title'; - youSellText.classList.add('offer-card-content-title'); - youSellText.innerText = 'Vendes'; - tradeDescDiv.append(youSellText); - - const youSellData = document.createElement('p'); - youSellData.classList.add('offer-card-content-data'); - if (this.wants === 'BTC') { - youSellData.innerText = `${this.trade_amount_eur} €`; - } - if (this.wants === 'EUR') { - youSellData.innerText = `${this.trade_amount_eur * 1021} sats`; - } - tradeDescDiv.append(youSellData); - - const premiumDescDiv = document.createElement('div'); - premiumDescDiv.classList.add('premium-desc'); - - const premiumTitle = document.createElement('p'); - premiumTitle.classList.add('offer-card-content-title'); - premiumTitle.innerText = 'Premium'; - premiumDescDiv.append(premiumTitle); - - const premiumData = document.createElement('p'); - premiumData.classList.add('offer-card-content-data'); - premiumData.innerText = `${this.premium * 100} %`; - premiumDescDiv.append(premiumData); - - const offerPriceTitle = document.createElement('p'); - offerPriceTitle.classList.add('offer-card-content-title'); - offerPriceTitle.innerText = 'Precio oferta'; - premiumDescDiv.append(offerPriceTitle); - - const offerPriceData = document.createElement('p'); - offerPriceData.classList.add('offer-card-content-data'); - offerPriceData.innerText = `90000 €/BTC`; - premiumDescDiv.append(offerPriceData); - - const marketPriceTitle = document.createElement('p'); - marketPriceTitle.classList.add('offer-card-content-title'); - marketPriceTitle.innerText = 'Precio mercado'; - premiumDescDiv.append(marketPriceTitle); - - const marketPriceData = document.createElement('p'); - marketPriceData.innerText = `88000 €/BTC`; - premiumDescDiv.append(marketPriceData); - - const whereDescDiv = document.createElement('div'); - whereDescDiv.classList.add('where-desc'); - - const whereDescTitle = document.createElement('p'); - whereDescTitle.classList.add('offer-card-content-title'); - whereDescTitle.innerText = 'Dónde'; - whereDescDiv.append(whereDescTitle); - - const whereDescData = document.createElement('p'); - whereDescData.classList.add('offer-long-text'); - whereDescData.innerText = `${this.location_details}`; - whereDescDiv.append(whereDescData); - - const whenDescDiv = document.createElement('div'); - whenDescDiv.classList.add('when-desc'); - - const whenDescTitle = document.createElement('p'); - whenDescTitle.classList.add('offer-card-content-title'); - whenDescTitle.innerText = 'Cúando'; - whenDescDiv.append(whenDescTitle); - - const whenDescData = document.createElement('p'); - whenDescData.classList.add('offer-long-text'); - whenDescData.innerText = `${this.time_availability_details}`; - whenDescDiv.append(whenDescData); - - const bitcoinMethodsDiv = document.createElement('div'); - bitcoinMethodsDiv.classList.add('bitcoin-methods-desc'); - - const bitcoinMethodsTitle = document.createElement('p'); - bitcoinMethodsTitle.classList.add('offer-card-content-title'); - bitcoinMethodsTitle.innerText = 'Protocolos Bitcoin aceptados'; - bitcoinMethodsDiv.append(bitcoinMethodsTitle); - - const onchainAcceptedContainer = document.createElement('div'); - onchainAcceptedContainer.classList.add('left-icon-checkboxed-field'); - if (this.is_onchain_accepted) { - const onchainIcon = document.createElement('img'); - onchainIcon.src = '/img/chains-lasecagold.svg'; - const onchainText = document.createElement('p'); - onchainText.innerText = 'Onchain'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-check-green.svg'; - - onchainAcceptedContainer.append(onchainIcon, onchainText, checkIcon); - } else { - const onchainIcon = document.createElement('img'); - onchainIcon.src = '/img/chains-gray.svg'; - const onchainText = document.createElement('p'); - onchainText.innerText = 'Onchain'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-xmark-gray.svg'; - - onchainAcceptedContainer.append(onchainIcon, onchainText, checkIcon); - } - const lightningAcceptedContainer = document.createElement('div'); - - lightningAcceptedContainer.classList.add('left-icon-checkboxed-field'); - if (this.is_lightning_accepted) { - const lightningIcon = document.createElement('img'); - lightningIcon.src = '/img/bolt-lightning-lasecagold.svg'; - const lightningText = document.createElement('p'); - lightningText.innerText = 'Lightning'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-check-green.svg'; - - lightningAcceptedContainer.append( - lightningIcon, - lightningText, - checkIcon - ); - } else { - const lightningIcon = document.createElement('img'); - lightningIcon.src = '/img/bolt-lightning-gray.svg'; - const lightningText = document.createElement('p'); - lightningText.innerText = 'Lightning'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-xmark-gray.svg'; - - lightningAcceptedContainer.append( - lightningIcon, - lightningText, - checkIcon - ); - } - - bitcoinMethodsDiv.append( - onchainAcceptedContainer, - lightningAcceptedContainer - ); - - const visibilityDiv = document.createElement('div'); - visibilityDiv.classList.add('visibility-desc'); - - const visibilityTitle = document.createElement('p'); - visibilityTitle.classList.add('offer-card-content-title'); - visibilityTitle.innerText = 'Visibilidad'; - visibilityDiv.append(visibilityTitle); - - const showOfferToTrustedContainer = document.createElement('div'); - showOfferToTrustedContainer.classList.add('right-icon-checkboxed-field'); - - if (this.show_offer_to_trusted) { - const showOfferToTrustedIcon = document.createElement('img'); - showOfferToTrustedIcon.src = '/img/user-lasecagold.svg'; - const showOfferToTrustedText = document.createElement('p'); - showOfferToTrustedText.innerText = 'Confiados'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-check-green.svg'; - - showOfferToTrustedContainer.append( - showOfferToTrustedIcon, - showOfferToTrustedText, - checkIcon - ); - } else { - const showOfferToTrustedIcon = document.createElement('img'); - showOfferToTrustedIcon.src = '/img/user-gray.svg'; - const showOfferToTrustedText = document.createElement('p'); - showOfferToTrustedText.innerText = 'Confiados'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-xmark-gray.svg'; - - showOfferToTrustedContainer.append( - showOfferToTrustedIcon, - showOfferToTrustedText, - checkIcon - ); - } - - const showOfferToTrustedTrustedContainer = document.createElement('div'); - showOfferToTrustedTrustedContainer.classList.add( - 'right-icon-checkboxed-field' - ); - - if (this.show_offer_to_trusted_trusted) { - const showOfferToTrustedTrustedIcon = document.createElement('img'); - showOfferToTrustedTrustedIcon.src = '/img/user-group-lasecagold.svg'; - const showOfferToTrustedTrustedText = document.createElement('p'); - showOfferToTrustedTrustedText.innerText = 'Sus confiados'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-check-green.svg'; - - showOfferToTrustedTrustedContainer.append( - showOfferToTrustedTrustedIcon, - showOfferToTrustedTrustedText, - checkIcon - ); - } else { - const showOfferToTrustedTrustedIcon = document.createElement('img'); - showOfferToTrustedTrustedIcon.src = '/img/user-group-gray.svg'; - const showOfferToTrustedTrustedText = document.createElement('p'); - showOfferToTrustedTrustedText.innerText = 'Sus confiados'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-xmark-gray.svg'; - - showOfferToTrustedTrustedContainer.append( - showOfferToTrustedTrustedIcon, - showOfferToTrustedTrustedText, - checkIcon - ); - } - - const showOfferToAllMembersContainer = document.createElement('div'); - showOfferToAllMembersContainer.classList.add('right-icon-checkboxed-field'); - - if (this.show_offer_to_all_members) { - const showOfferToAllMembersIcon = document.createElement('img'); - showOfferToAllMembersIcon.src = '/img/many-users-lasecagold.svg'; - const showOfferToAllMembersText = document.createElement('p'); - showOfferToAllMembersText.innerText = 'Todos'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-check-green.svg'; - - showOfferToAllMembersContainer.append( - showOfferToAllMembersIcon, - showOfferToAllMembersText, - checkIcon - ); - } else { - const showOfferToAllMembersIcon = document.createElement('img'); - showOfferToAllMembersIcon.src = '/img/many-users-gray.svg'; - const showOfferToAllMembersText = document.createElement('p'); - showOfferToAllMembersText.innerText = 'Todos'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-xmark-gray.svg'; - - showOfferToAllMembersContainer.append( - showOfferToAllMembersIcon, - showOfferToAllMembersText, - checkIcon - ); - } - visibilityDiv.append( - showOfferToTrustedContainer, - showOfferToTrustedTrustedContainer, - showOfferToAllMembersContainer - ); - - const otherOfferFeaturesDiv = document.createElement('div'); - otherOfferFeaturesDiv.classList.add('other-desc'); - - const otherOfferFeaturesTitle = document.createElement('p'); - otherOfferFeaturesTitle.classList.add('offer-card-content-title'); - otherOfferFeaturesTitle.innerText = 'Otros'; - otherOfferFeaturesDiv.append(otherOfferFeaturesTitle); - - const areBigNotesAcceptedContainer = document.createElement('div'); - areBigNotesAcceptedContainer.classList.add('left-icon-checkboxed-field'); - - if (this.are_big_notes_accepted) { - const areBigNotesAcceptedIcon = document.createElement('img'); - areBigNotesAcceptedIcon.src = '/img/eur-bill-lasecagold.svg'; - const areBigNotesAcceptedText = document.createElement('p'); - areBigNotesAcceptedText.innerText = 'Billetes grandes'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-check-green.svg'; - - areBigNotesAcceptedContainer.append( - areBigNotesAcceptedIcon, - areBigNotesAcceptedText, - checkIcon - ); - } else { - const areBigNotesAcceptedIcon = document.createElement('img'); - areBigNotesAcceptedIcon.src = '/img/eur-bill-gray.svg'; - const areBigNotesAcceptedText = document.createElement('p'); - areBigNotesAcceptedText.innerText = 'Billetes grandes'; - const checkIcon = document.createElement('img'); - checkIcon.src = '/img/circle-xmark-gray.svg'; - - areBigNotesAcceptedContainer.append( - areBigNotesAcceptedIcon, - areBigNotesAcceptedText, - checkIcon - ); - } - - otherOfferFeaturesDiv.append(areBigNotesAcceptedContainer); - - const actionButtonsArea = document.createElement('p'); - actionButtonsArea.classList.add('offer-action-buttons-area'); - - const editActionArea = document.createElement('div'); - editActionArea.classList.add('offer-action-area'); - editActionArea.classList.add('subtle-box'); - const editActionIcon = document.createElement('img'); - editActionIcon.src = '/img/edit.svg'; - const editActionText = document.createElement('p'); - editActionText.innerText = 'Editar'; - editActionArea.append(editActionIcon, editActionText); - - const deleteActionArea = document.createElement('div'); - deleteActionArea.classList.add('offer-action-area'); - deleteActionArea.classList.add('subtle-box'); - const deleteActionIcon = document.createElement('img'); - deleteActionIcon.src = '/img/trash-can-darkred.svg'; - const deleteActionText = document.createElement('p'); - deleteActionText.innerText = 'Eliminar'; - deleteActionArea.append(deleteActionIcon, deleteActionText); - deleteActionArea.addEventListener('click', async () => { - await deleteOfferByUuid(this.uuid); - await myOffers.getOffersFromApi(); - await myOffers.render(); - toggleOfferDeletedAlert(); - }); - - actionButtonsArea.append(editActionArea, deleteActionArea); - - offerCard.append( - tradeDescDiv, - premiumDescDiv, - whereDescDiv, - whenDescDiv, - bitcoinMethodsDiv, - visibilityDiv, - otherOfferFeaturesDiv, - actionButtonsArea - ); - - return offerCard; - } -} - -class MyOffers { - constructor(ownOffersContainerElement) { - this.ownOffersContainerElement = ownOffersContainerElement; - this.offers = []; - } - - async getOffersFromApi() { - const offersResponse = await fetch('/api/publickey-offers'); - - this.offers = []; - - const offersData = (await offersResponse.json()).data; - if (offersResponse.ok) { - for (const record of offersData) { - this.offers.push(new Offer(record)); - } - } - } - - async render() { - if (this.offers.length === 0) { - this.ownOffersContainerElement.innerHTML = - '

Vaya, no hay nada por aquí...

'; - return; - } - this.ownOffersContainerElement.innerHTML = ''; - - for (const someOffer of this.offers) { - this.ownOffersContainerElement.append(someOffer.buildHTML()); - } - } -} - -async function deleteOfferByUuid(offerUuid) { - await fetch(`/api/offer/${offerUuid}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - }); -} - -buttonStartCreateOffer.addEventListener('click', () => { - toggleCreateOfferModal(); -}); - -buttonViewMyOffers.addEventListener('click', async () => { - await myOffers.getOffersFromApi(); - await myOffers.render(); - toggleViewMyOffersPanel(); -}); - -closeOffer.addEventListener('click', () => { - toggleCreateOfferModal(); -}); - -buyOrSellButtons.forEach((button) => { - button.addEventListener('click', () => { - toggleBuyOrSellButtonGroup(); - }); -}); - -buttonIncreasePremium.addEventListener('click', () => { - modifyPremiumValue(1); -}); - -buttonDecreasePremium.addEventListener('click', () => { - modifyPremiumValue(-1); -}); - -eurAmountInput.addEventListener('blur', () => { - validateAndFormatEurAmountInput(); - updateBtcInput(); -}); - -eurAmountInput.addEventListener('input', () => { - eurAmountInput.value = eurAmountInput.value.replace(/[^0-9]/g, ''); - updateBtcInput(); -}); - -for (const btcMethodCheckbox of btcMethodCheckboxes) { - btcMethodCheckbox.addEventListener('click', () => { - validateBitcoinMethodCheckboxes(btcMethodCheckbox); - }); -} - -myTrustedTrustedCheckbox.addEventListener('click', () => { - applyTrustCheckboxConstraints(myTrustedTrustedCheckbox); -}); - -allMembersCheckbox.addEventListener('click', () => { - applyTrustCheckboxConstraints(allMembersCheckbox); -}); - -publishOfferButton.addEventListener('click', async () => { - await publishOffer(); - await myOffers.getOffersFromApi(); - await myOffers.render(); -}); - -updateBtcInput(); - -const myOffers = new MyOffers(ownOffersContainer); From f034f29d94ad4686d40aac2f7daae1cd7d6ad990 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 12 Mar 2025 18:56:36 +0100 Subject: [PATCH 200/239] install webpack --- package-lock.json | 1118 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 6 +- 2 files changed, 1122 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bea970..4068beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,18 @@ "playwright": "^1.50.1", "prettier": "^3.5.1", "prettier-plugin-ejs": "^1.0.3", - "sequelize-cli": "^6.6.2" + "sequelize-cli": "^6.6.2", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "engines": { + "node": ">=14.17.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -290,6 +301,64 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@noble/ciphers": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", @@ -494,6 +563,38 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -517,6 +618,208 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -629,6 +932,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -775,6 +1117,38 @@ "concat-map": "0.0.1" } }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -798,6 +1172,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -870,6 +1250,26 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001703", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", + "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -893,6 +1293,15 @@ "node": ">=10" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -929,6 +1338,20 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -954,6 +1377,12 @@ "color-support": "bin.js" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/commander": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", @@ -1243,6 +1672,12 @@ "node": ">=0.10.0" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.114", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.114.tgz", + "integrity": "sha512-DFptFef3iktoKlFQK/afbo274/XNWD00Am0xa7M8FZUepHlHT8PEuiNBoRfFHbH1okqN58AlhbJ4QTkcnXorjA==", + "dev": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1286,6 +1721,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -1295,6 +1743,18 @@ "node": ">=6" } }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -1317,6 +1777,12 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1658,6 +2124,15 @@ "es5-ext": "~0.10.14" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -1741,6 +2216,31 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", @@ -1824,6 +2324,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -2044,6 +2553,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "node_modules/globals": { "version": "15.15.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", @@ -2270,6 +2785,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2321,6 +2855,15 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -2399,6 +2942,18 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -2410,6 +2965,15 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -2442,6 +3006,35 @@ "node": ">=10" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-beautify": { "version": "1.15.4", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", @@ -2571,6 +3164,12 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2601,6 +3200,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2613,6 +3221,15 @@ "node": ">= 0.8.0" } }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2728,6 +3345,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2942,6 +3565,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -2988,6 +3617,12 @@ "node": ">= 10.12.0" } }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -3138,6 +3773,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -3310,6 +3954,76 @@ "split2": "^4.1.0" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/playwright": { "version": "1.50.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", @@ -3531,6 +4245,15 @@ } ] }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3580,6 +4303,18 @@ "node": ">= 6" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3589,6 +4324,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -3609,6 +4353,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3701,6 +4466,59 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -3868,6 +4686,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -3893,6 +4720,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4090,6 +4929,25 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "optional": true }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -4255,6 +5113,15 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -4310,6 +5177,64 @@ "node": ">=8" } }, + "node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4450,6 +5375,36 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4499,6 +5454,161 @@ "node": ">= 0.8" } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.98.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4522,6 +5632,12 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, "node_modules/wkx": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", diff --git a/package.json b/package.json index 26f47dc..a1170cb 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "start": "node src/app.js", "start:container": "docker compose up -d --build", "stop:container": "docker compose down", + "build": "webpack", + "watch": "webpack --watch", "cli": "node src/cli.js", "test": "playwright test", "lint": "eslint . --fix", @@ -37,6 +39,8 @@ "playwright": "^1.50.1", "prettier": "^3.5.1", "prettier-plugin-ejs": "^1.0.3", - "sequelize-cli": "^6.6.2" + "sequelize-cli": "^6.6.2", + "webpack": "^5.98.0", + "webpack-cli": "^6.0.1" } } From 5b6ab8779e19aa8ae936b70c73b5add41efb39b7 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 12 Mar 2025 18:56:53 +0100 Subject: [PATCH 201/239] configure webpack --- webpack.config.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 webpack.config.js diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..6cedde0 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,12 @@ +const path = require('path'); + +module.exports = { + entry: { + invite: './src/front/pages/invite.js', + }, + output: { + filename: '[name].bundle.js', + path: path.resolve(__dirname, 'public'), + }, + mode: 'development', +}; From ae64424f54cffc846ac7114f99c697fc47ff0999 Mon Sep 17 00:00:00 2001 From: counterweight Date: Wed, 12 Mar 2025 18:57:17 +0100 Subject: [PATCH 202/239] refactor invite page to pickup webpacked code --- public/javascript/invite.js | 74 --------------------------------- src/front/pages/invite.js | 83 +++++++++++++++++++++++++++++++++++++ src/views/invite.ejs | 6 +-- 3 files changed, 86 insertions(+), 77 deletions(-) delete mode 100644 public/javascript/invite.js create mode 100644 src/front/pages/invite.js diff --git a/public/javascript/invite.js b/public/javascript/invite.js deleted file mode 100644 index 70bb66d..0000000 --- a/public/javascript/invite.js +++ /dev/null @@ -1,74 +0,0 @@ -window.onload = function () { - if (!window.nostr) { - console.log('Nostr extension not present'); - document.querySelector('#nostr-signup-button').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; - } else { - console.log('Nostr extension present'); - } -}; - -const signUpConfirmation = document.querySelector('#sign-up-success'); - -function showConfirmationAndRedirect() { - signUpConfirmation.classList.add('revealed'); - setTimeout(() => { - window.location.href = '/createProfile'; - }, 5000); -} - -async function acceptInvite() { - let challengeResponse; - try { - challengeResponse = await fetch('/api/signup/nostr-challenge', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - const { challenge } = await challengeResponse.json(); - - let pubkey; - try { - pubkey = await window.nostr.getPublicKey(); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - const event = { - kind: 22242, - created_at: Math.floor(Date.now() / 1000), - tags: [['challenge', challenge]], - content: 'Sign this challenge to authenticate', - pubkey: pubkey, - }; - - let signedEvent; - try { - signedEvent = await window.nostr.signEvent(event); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - - let verifyResponse; - try { - verifyResponse = await fetch('/api/signup/nostr-verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signedEvent), - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - if (verifyResponse.ok) { - showConfirmationAndRedirect(); - } -} diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js new file mode 100644 index 0000000..c5b947e --- /dev/null +++ b/src/front/pages/invite.js @@ -0,0 +1,83 @@ +const invitesFunction = () => { + window.onload = function () { + if (!window.nostr) { + console.log('Nostr extension not present'); + document.querySelector('#nostr-signup-button').disabled = true; + document.querySelector('#no-extension-nudges').style.display = 'block'; + } else { + console.log('Nostr extension present'); + } + }; + + const signUpConfirmation = document.querySelector('#sign-up-success'); + + function showConfirmationAndRedirect() { + signUpConfirmation.classList.add('revealed'); + setTimeout(() => { + window.location.href = '/createProfile'; + }, 5000); + } + + async function acceptInvite() { + let challengeResponse; + try { + challengeResponse = await fetch('/api/signup/nostr-challenge', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + const { challenge } = await challengeResponse.json(); + + let pubkey; + try { + pubkey = await window.nostr.getPublicKey(); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + const event = { + kind: 22242, + created_at: Math.floor(Date.now() / 1000), + tags: [['challenge', challenge]], + content: 'Sign this challenge to authenticate', + pubkey: pubkey, + }; + + let signedEvent; + try { + signedEvent = await window.nostr.signEvent(event); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + + let verifyResponse; + try { + verifyResponse = await fetch('/api/signup/nostr-verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signedEvent), + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + if (verifyResponse.ok) { + showConfirmationAndRedirect(); + } + } + + const signUpButton = document.getElementById('nostr-signup-button'); + signUpButton.addEventListener('click', () => { + acceptInvite(); + }); +}; + +invitesFunction(); diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 3957b59..d5e4dc9 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -21,7 +21,7 @@ />

Usa tu extensión de Nostr para darte de alta:

- +
- +
- + From eb1cfbb64c3f92d6e94ae3a802f28b0379fce0d4 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:05:12 +0100 Subject: [PATCH 203/239] next page --- public/javascript/createProfile.js | 121 ---------------------------- src/front/pages/createProfile.js | 125 +++++++++++++++++++++++++++++ src/views/createProfile.ejs | 2 +- src/views/invite.ejs | 2 +- webpack.config.js | 3 +- 5 files changed, 129 insertions(+), 124 deletions(-) delete mode 100644 public/javascript/createProfile.js create mode 100644 src/front/pages/createProfile.js diff --git a/public/javascript/createProfile.js b/public/javascript/createProfile.js deleted file mode 100644 index 4be8920..0000000 --- a/public/javascript/createProfile.js +++ /dev/null @@ -1,121 +0,0 @@ -const createProfileConfirmation = document.querySelector( - '#create-profile-success' -); - -function debounce(func, wait) { - let timeout; - return function (...args) { - clearTimeout(timeout); - timeout = setTimeout(() => func.apply(this, args), wait); - }; -} - -const validateNymInput = debounce(() => { - const nymValue = nymInput.value.trim(); - const isValid = nymValue.length >= 3 && nymValue.length <= 128; - if (isValid) { - nymInputValidationWarning.style.display = 'none'; - } else { - nymInputValidationWarning.style.display = 'block'; - } -}, 500); - -const checkIfSubmittable = debounce((allInputs) => { - const nymIsFilled = allInputs.nymInput.value !== ''; - let atLeastOneContactIsFilled = false; - - for (const contactInput of allInputs.contactInputs) { - if (contactInput.value !== '') { - atLeastOneContactIsFilled = true; - } - } - - const buttonShouldBeDisabled = !(nymIsFilled && atLeastOneContactIsFilled); - submitProfileButton.disabled = buttonShouldBeDisabled; -}, 500); - -async function createProfile(allInputs) { - const contactDetails = []; - for (const someInput of allInputs.contactInputs) { - contactDetails.push({ - type: someInput.getAttribute('data-type'), - value: someInput.value, - }); - } - const encryptedContactDetails = await window.nostr.nip04.encrypt( - await window.nostr.getPublicKey(), - JSON.stringify(contactDetails) - ); - await fetch('/api/set-contact-details', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ encryptedContactDetails }), - }); - - const nym = allInputs.nymInput.value; - await fetch('/api/set-nym', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ nym }), - }); - - createProfileConfirmation.classList.add('revealed'); - setTimeout(() => { - window.location.href = '/home'; - }, 5000); -} - -function onLoadErrands(allInputs, submitProfileButton) { - allInputs.nymInput.addEventListener('input', validateNymInput); - - for (const someInput of allInputs.allInputs) { - someInput.addEventListener('input', () => { - checkIfSubmittable(allInputs); - }); - } - - checkIfSubmittable(allInputs); - - submitProfileButton.addEventListener('click', () => { - createProfile(allInputs); - }); -} - -const nymInput = document.getElementById('nym-input'); -const nymInputValidationWarning = document.getElementById( - 'nym-input-validation-warning' -); -const phoneInput = document.getElementById('phone-input'); -const whatsappInput = document.getElementById('whatsapp-input'); -const telegramInput = document.getElementById('telegram-input'); -const emailInput = document.getElementById('email-input'); -const nostrInput = document.getElementById('nostr-input'); -const signalInput = document.getElementById('signal-input'); -const submitProfileButton = document.getElementById('submit-profile-button'); - -const allInputs = { - nymInput: nymInput, - contactInputs: [ - phoneInput, - whatsappInput, - telegramInput, - emailInput, - nostrInput, - signalInput, - ], - allInputs: [ - nymInput, - phoneInput, - whatsappInput, - telegramInput, - emailInput, - nostrInput, - signalInput, - ], -}; - -onLoadErrands(allInputs, submitProfileButton); diff --git a/src/front/pages/createProfile.js b/src/front/pages/createProfile.js new file mode 100644 index 0000000..51e61dd --- /dev/null +++ b/src/front/pages/createProfile.js @@ -0,0 +1,125 @@ +const createProfilesFunction = () => { + const createProfileConfirmation = document.querySelector( + '#create-profile-success' + ); + + function debounce(func, wait) { + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; + } + + const validateNymInput = debounce(() => { + const nymValue = nymInput.value.trim(); + const isValid = nymValue.length >= 3 && nymValue.length <= 128; + if (isValid) { + nymInputValidationWarning.style.display = 'none'; + } else { + nymInputValidationWarning.style.display = 'block'; + } + }, 500); + + const checkIfSubmittable = debounce((allInputs) => { + const nymIsFilled = allInputs.nymInput.value !== ''; + let atLeastOneContactIsFilled = false; + + for (const contactInput of allInputs.contactInputs) { + if (contactInput.value !== '') { + atLeastOneContactIsFilled = true; + } + } + + const buttonShouldBeDisabled = !(nymIsFilled && atLeastOneContactIsFilled); + submitProfileButton.disabled = buttonShouldBeDisabled; + }, 500); + + async function createProfile(allInputs) { + const contactDetails = []; + for (const someInput of allInputs.contactInputs) { + contactDetails.push({ + type: someInput.getAttribute('data-type'), + value: someInput.value, + }); + } + const encryptedContactDetails = await window.nostr.nip04.encrypt( + await window.nostr.getPublicKey(), + JSON.stringify(contactDetails) + ); + await fetch('/api/set-contact-details', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ encryptedContactDetails }), + }); + + const nym = allInputs.nymInput.value; + await fetch('/api/set-nym', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ nym }), + }); + + createProfileConfirmation.classList.add('revealed'); + setTimeout(() => { + window.location.href = '/home'; + }, 5000); + } + + function onLoadErrands(allInputs, submitProfileButton) { + allInputs.nymInput.addEventListener('input', validateNymInput); + + for (const someInput of allInputs.allInputs) { + someInput.addEventListener('input', () => { + checkIfSubmittable(allInputs); + }); + } + + checkIfSubmittable(allInputs); + + submitProfileButton.addEventListener('click', () => { + createProfile(allInputs); + }); + } + + const nymInput = document.getElementById('nym-input'); + const nymInputValidationWarning = document.getElementById( + 'nym-input-validation-warning' + ); + const phoneInput = document.getElementById('phone-input'); + const whatsappInput = document.getElementById('whatsapp-input'); + const telegramInput = document.getElementById('telegram-input'); + const emailInput = document.getElementById('email-input'); + const nostrInput = document.getElementById('nostr-input'); + const signalInput = document.getElementById('signal-input'); + const submitProfileButton = document.getElementById('submit-profile-button'); + + const allInputs = { + nymInput: nymInput, + contactInputs: [ + phoneInput, + whatsappInput, + telegramInput, + emailInput, + nostrInput, + signalInput, + ], + allInputs: [ + nymInput, + phoneInput, + whatsappInput, + telegramInput, + emailInput, + nostrInput, + signalInput, + ], + }; + + onLoadErrands(allInputs, submitProfileButton); +}; + +createProfilesFunction(); diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs index 3613440..d4be5ec 100644 --- a/src/views/createProfile.ejs +++ b/src/views/createProfile.ejs @@ -130,5 +130,5 @@
- + diff --git a/src/views/invite.ejs b/src/views/invite.ejs index d5e4dc9..d7d8f79 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -110,6 +110,6 @@
- + diff --git a/webpack.config.js b/webpack.config.js index 6cedde0..9e61a4e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,10 +3,11 @@ const path = require('path'); module.exports = { entry: { invite: './src/front/pages/invite.js', + createProfile: './src/front/pages/createProfile.js', }, output: { filename: '[name].bundle.js', - path: path.resolve(__dirname, 'public'), + path: path.resolve(__dirname, 'public', 'javascript'), }, mode: 'development', }; From 9dbe299a3260976d972512670f30639a02db4eff Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:08:54 +0100 Subject: [PATCH 204/239] login page --- public/javascript/home.js | 0 public/javascript/login.js | 72 --------------------------------- src/front/pages/login.js | 81 ++++++++++++++++++++++++++++++++++++++ src/views/login.ejs | 6 +-- webpack.config.js | 1 + 5 files changed, 85 insertions(+), 75 deletions(-) delete mode 100644 public/javascript/home.js delete mode 100644 public/javascript/login.js create mode 100644 src/front/pages/login.js diff --git a/public/javascript/home.js b/public/javascript/home.js deleted file mode 100644 index e69de29..0000000 diff --git a/public/javascript/login.js b/public/javascript/login.js deleted file mode 100644 index 36175b4..0000000 --- a/public/javascript/login.js +++ /dev/null @@ -1,72 +0,0 @@ -window.onload = function () { - if (!window.nostr) { - console.log('Nostr extension not present'); - document.querySelector('#login-button').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; - } else { - console.log('Nostr extension present'); - } -}; - -async function login() { - let challengeResponse; - try { - challengeResponse = await fetch('/api/login/nostr-challenge', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - const { challenge } = await challengeResponse.json(); - - let pubkey; - try { - pubkey = await window.nostr.getPublicKey(); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - const event = { - kind: 22242, - created_at: Math.floor(Date.now() / 1000), - tags: [['challenge', challenge]], - content: 'Sign this challenge to authenticate', - pubkey: pubkey, - }; - - let signedEvent; - try { - signedEvent = await window.nostr.signEvent(event); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - - let verifyResponse; - try { - verifyResponse = await fetch('/api/login/nostr-verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signedEvent), - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - if (verifyResponse.status === 403) { - document.querySelector('#rejected-public-key').style.display = 'block'; - } - - if (verifyResponse.ok) { - document.querySelector('#sign-up-success').style.display = 'block'; - setTimeout(() => { - window.location.href = '/home'; - }, 1000); - } -} diff --git a/src/front/pages/login.js b/src/front/pages/login.js new file mode 100644 index 0000000..121f3e1 --- /dev/null +++ b/src/front/pages/login.js @@ -0,0 +1,81 @@ +const loginsFunction = () => { + window.onload = function () { + if (!window.nostr) { + console.log('Nostr extension not present'); + document.querySelector('#login-button').disabled = true; + document.querySelector('#no-extension-nudges').style.display = 'block'; + } else { + console.log('Nostr extension present'); + } + }; + + async function login() { + let challengeResponse; + try { + challengeResponse = await fetch('/api/login/nostr-challenge', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + const { challenge } = await challengeResponse.json(); + + let pubkey; + try { + pubkey = await window.nostr.getPublicKey(); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + const event = { + kind: 22242, + created_at: Math.floor(Date.now() / 1000), + tags: [['challenge', challenge]], + content: 'Sign this challenge to authenticate', + pubkey: pubkey, + }; + + let signedEvent; + try { + signedEvent = await window.nostr.signEvent(event); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + + let verifyResponse; + try { + verifyResponse = await fetch('/api/login/nostr-verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signedEvent), + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + if (verifyResponse.status === 403) { + document.querySelector('#rejected-public-key').style.display = 'block'; + } + + if (verifyResponse.ok) { + document.querySelector('#sign-up-success').style.display = 'block'; + setTimeout(() => { + window.location.href = '/home'; + }, 1000); + } + } + + const loginButton = document.getElementById('login-button'); + loginButton.addEventListener('click', () => { + login(); + }); +}; + +loginsFunction(); diff --git a/src/views/login.ejs b/src/views/login.ejs index a4467ec..a954697 100644 --- a/src/views/login.ejs +++ b/src/views/login.ejs @@ -13,7 +13,7 @@
-
+
- +
- + diff --git a/webpack.config.js b/webpack.config.js index 9e61a4e..e51ce85 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ module.exports = { entry: { invite: './src/front/pages/invite.js', createProfile: './src/front/pages/createProfile.js', + login: './src/front/pages/login.js', }, output: { filename: '[name].bundle.js', From 161135e3152800f96f46f88ba4b6972486b41d5f Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:16:01 +0100 Subject: [PATCH 205/239] offers --- {public/javascript => src/front/pages}/offers.js | 2 +- src/views/offers.ejs | 2 +- webpack.config.js | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename {public/javascript => src/front/pages}/offers.js (99%) diff --git a/public/javascript/offers.js b/src/front/pages/offers.js similarity index 99% rename from public/javascript/offers.js rename to src/front/pages/offers.js index 4e57b19..b619ab0 100644 --- a/public/javascript/offers.js +++ b/src/front/pages/offers.js @@ -694,4 +694,4 @@ function offersPage() { const myOffers = new MyOffers(ownOffersContainer); } -offersPage; +offersPage(); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index b04d0a1..2c99fe7 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -210,6 +210,6 @@
<%- include("partials/appCommonScripts") %> - + diff --git a/webpack.config.js b/webpack.config.js index e51ce85..9312cea 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ module.exports = { invite: './src/front/pages/invite.js', createProfile: './src/front/pages/createProfile.js', login: './src/front/pages/login.js', + offers: './src/front/pages/offers.js', }, output: { filename: '[name].bundle.js', From 0f7ec6f521f5cb02b2e048cd0c2f9a3efc3dbe48 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:16:58 +0100 Subject: [PATCH 206/239] inline utils --- public/javascript/utils.js | 3 --- src/front/pages/offers.js | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 public/javascript/utils.js diff --git a/public/javascript/utils.js b/public/javascript/utils.js deleted file mode 100644 index e8964e2..0000000 --- a/public/javascript/utils.js +++ /dev/null @@ -1,3 +0,0 @@ -function formatNumberWithSpaces(num) { - return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); -} diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index b619ab0..e85add2 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,4 +1,8 @@ function offersPage() { + function formatNumberWithSpaces(num) { + return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); + } + const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); From 17517f798d456c9a6687daf79ba65cf95256256b Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:28:07 +0100 Subject: [PATCH 207/239] home --- src/front/pages/home.js | 14 ++++++++++++++ src/front/pages/offers.js | 11 +++++++++++ src/views/home.ejs | 2 +- webpack.config.js | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/front/pages/home.js diff --git a/src/front/pages/home.js b/src/front/pages/home.js new file mode 100644 index 0000000..92bd6bb --- /dev/null +++ b/src/front/pages/home.js @@ -0,0 +1,14 @@ +const homeFunction = () => { + const navbuttonHome = document.getElementById('navbutton-home'); + const navbuttonOffers = document.getElementById('navbutton-offers'); + + navbuttonHome.addEventListener('click', () => { + window.location.href = '/home'; + }); + + navbuttonOffers.addEventListener('click', () => { + window.location.href = '/offers'; + }); +}; + +homeFunction(); diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index e85add2..b0954d3 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -3,6 +3,17 @@ function offersPage() { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); } + const navbuttonHome = document.getElementById('navbutton-home'); + const navbuttonOffers = document.getElementById('navbutton-offers'); + + navbuttonHome.addEventListener('click', () => { + window.location.href = '/home'; + }); + + navbuttonOffers.addEventListener('click', () => { + window.location.href = '/offers'; + }); + const buttonStartCreateOffer = document.getElementById( 'button-start-create-offer' ); diff --git a/src/views/home.ejs b/src/views/home.ejs index 9584e63..a764e25 100644 --- a/src/views/home.ejs +++ b/src/views/home.ejs @@ -11,6 +11,6 @@ <%- include("partials/appCommonHeader") %> <%- include("partials/appCommonScripts") %> - + diff --git a/webpack.config.js b/webpack.config.js index 9312cea..86f8a98 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ module.exports = { createProfile: './src/front/pages/createProfile.js', login: './src/front/pages/login.js', offers: './src/front/pages/offers.js', + home: './src/front/pages/home.js', }, output: { filename: '[name].bundle.js', From faf8d61ef838405bc95e0662d27b65511286c8bb Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:28:13 +0100 Subject: [PATCH 208/239] home --- public/javascript/app.js | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 public/javascript/app.js diff --git a/public/javascript/app.js b/public/javascript/app.js deleted file mode 100644 index aa28857..0000000 --- a/public/javascript/app.js +++ /dev/null @@ -1,10 +0,0 @@ -const navbuttonHome = document.getElementById('navbutton-home'); -const navbuttonOffers = document.getElementById('navbutton-offers'); - -navbuttonHome.addEventListener('click', () => { - window.location.href = '/home'; -}); - -navbuttonOffers.addEventListener('click', () => { - window.location.href = '/offers'; -}); From 5a7c9549821646aa0b1b506f183066ea4b917457 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:33:10 +0100 Subject: [PATCH 209/239] ignore bundle files --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 846e04f..d83cd91 100644 --- a/.gitignore +++ b/.gitignore @@ -136,10 +136,10 @@ dist .pnp.* -test-results/* - -# Playwright /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ + +# webpack bundles +/public/javascript/* \ No newline at end of file From 0b3e5e83a3c4f602ad73237985a283b24cf7b8cc Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:45:28 +0100 Subject: [PATCH 210/239] extract util --- src/front/pages/invite.js | 22 ++++++++++++++-------- src/front/utils/checkNostrExtension.js | 9 +++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 src/front/utils/checkNostrExtension.js diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index c5b947e..bca4e92 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -1,12 +1,18 @@ +const checkNostrExtension = require('../utils/checkNostrExtension'); + const invitesFunction = () => { - window.onload = function () { - if (!window.nostr) { - console.log('Nostr extension not present'); - document.querySelector('#nostr-signup-button').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; - } else { - console.log('Nostr extension present'); - } + window.onload = () => { + checkNostrExtension( + window, + () => { + console.log('Nostr extension present'); + }, + () => { + console.log('Nostr extension not present'); + document.querySelector('#nostr-signup-button').disabled = true; + document.querySelector('#no-extension-nudges').style.display = 'block'; + } + ); }; const signUpConfirmation = document.querySelector('#sign-up-success'); diff --git a/src/front/utils/checkNostrExtension.js b/src/front/utils/checkNostrExtension.js new file mode 100644 index 0000000..b1d9610 --- /dev/null +++ b/src/front/utils/checkNostrExtension.js @@ -0,0 +1,9 @@ +function checkNostrExtension(window, successCallback, failureCallback) { + if (!window.nostr) { + failureCallback(); + } else { + successCallback(); + } +} + +module.exports = checkNostrExtension; From 8131de0c96b65976ab9b23301f5dbfc4cb8a9464 Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 11:48:40 +0100 Subject: [PATCH 211/239] use util --- src/front/pages/login.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/front/pages/login.js b/src/front/pages/login.js index 121f3e1..752c376 100644 --- a/src/front/pages/login.js +++ b/src/front/pages/login.js @@ -1,12 +1,18 @@ +const checkNostrExtension = require('../utils/checkNostrExtension'); + const loginsFunction = () => { - window.onload = function () { - if (!window.nostr) { - console.log('Nostr extension not present'); - document.querySelector('#login-button').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; - } else { - console.log('Nostr extension present'); - } + window.onload = () => { + checkNostrExtension( + window, + () => { + console.log('Nostr extension present'); + }, + () => { + console.log('Nostr extension not present'); + document.querySelector('#login-button').disabled = true; + document.querySelector('#no-extension-nudges').style.display = 'block'; + } + ); }; async function login() { From 545c54bf813e4eaaf27319dc1c95aa18fc4a5ffb Mon Sep 17 00:00:00 2001 From: counterweight Date: Thu, 13 Mar 2025 15:36:00 +0100 Subject: [PATCH 212/239] stuff really --- src/constants.js | 7 ++ src/front/components/NostrSignUpButton.js | 0 src/front/pages/invite.js | 125 +++++++++++----------- src/front/services/signupService.js | 57 ++++++++++ src/views/invite.ejs | 17 +-- 5 files changed, 130 insertions(+), 76 deletions(-) create mode 100644 src/front/components/NostrSignUpButton.js create mode 100644 src/front/services/signupService.js diff --git a/src/constants.js b/src/constants.js index 8bec536..79c5699 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,7 +1,14 @@ const DEFAULT_SESSION_DURATION_SECONDS = 60 * 60 * 24 * 30; const DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS = 60 * 60 * 24 * 30; +const DEFAULT_REDIRECT_DELAY = 3 * 1000; // 3seconds times milliseconds; + +const API_PATHS = { + createProfile: '/createProfile', +}; module.exports = { DEFAULT_SESSION_DURATION_SECONDS, DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS, + API_PATHS, + DEFAULT_REDIRECT_DELAY, }; diff --git a/src/front/components/NostrSignUpButton.js b/src/front/components/NostrSignUpButton.js new file mode 100644 index 0000000..e69de29 diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index bca4e92..d8f1672 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -1,6 +1,63 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); +const signupService = require('../services/signupService'); +const constants = require('../../constants'); + +class NostrSignupButton { + constructor({ parentElement, id, onClickCallback }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.onClickCallback = onClickCallback; + } + + render() { + const thisButton = document.createElement('button'); + thisButton.id = this.id; + thisButton.type = 'submit'; + thisButton.className = 'button-large button-nostr'; + + const figure = document.createElement('figure'); + + const img = document.createElement('img'); + img.src = '/img/white_ostrich.svg'; + img.style.width = '40%'; + img.style.margin = '-5% -5%'; + + figure.appendChild(img); + + const paragraph = document.createElement('p'); + paragraph.textContent = 'Alta con Nostr'; + + thisButton.appendChild(figure); + thisButton.appendChild(paragraph); + + thisButton.addEventListener('click', () => { + this.onClickCallback(); + }); + + this.element = thisButton; + this.parentElement.appendChild(this.element); + } + + disable() { + this.element.disabled = true; + } +} + +const nostrSignupArea = document.getElementById('nostr-signup-area'); +const noExtensionNudges = document.getElementById('no-extension-nudges'); +const signUpSuccessNotification = document.getElementById('sign-up-success'); const invitesFunction = () => { + const signupButton = new NostrSignupButton({ + parentElement: nostrSignupArea, + id: 'nostr-signup-button', + onClickCallback: () => { + acceptInvite(); + }, + }); + signupButton.render(); + window.onload = () => { checkNostrExtension( window, @@ -9,81 +66,27 @@ const invitesFunction = () => { }, () => { console.log('Nostr extension not present'); - document.querySelector('#nostr-signup-button').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; + signupButton.disable(); + noExtensionNudges.style.display = 'block'; } ); }; - const signUpConfirmation = document.querySelector('#sign-up-success'); - function showConfirmationAndRedirect() { - signUpConfirmation.classList.add('revealed'); + signUpSuccessNotification.classList.add('revealed'); setTimeout(() => { - window.location.href = '/createProfile'; - }, 5000); + window.location.href = constants.API_PATHS.createProfile; + }, constants.DEFAULT_REDIRECT_DELAY); } async function acceptInvite() { - let challengeResponse; - try { - challengeResponse = await fetch('/api/signup/nostr-challenge', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - const { challenge } = await challengeResponse.json(); - - let pubkey; - try { - pubkey = await window.nostr.getPublicKey(); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - const event = { - kind: 22242, - created_at: Math.floor(Date.now() / 1000), - tags: [['challenge', challenge]], - content: 'Sign this challenge to authenticate', - pubkey: pubkey, - }; - - let signedEvent; - try { - signedEvent = await window.nostr.signEvent(event); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - - let verifyResponse; - try { - verifyResponse = await fetch('/api/signup/nostr-verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signedEvent), - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } + const verifyResponse = + await signupService.requestAndRespondSignUpChallenge(); if (verifyResponse.ok) { showConfirmationAndRedirect(); } } - - const signUpButton = document.getElementById('nostr-signup-button'); - signUpButton.addEventListener('click', () => { - acceptInvite(); - }); }; invitesFunction(); diff --git a/src/front/services/signupService.js b/src/front/services/signupService.js new file mode 100644 index 0000000..03f2469 --- /dev/null +++ b/src/front/services/signupService.js @@ -0,0 +1,57 @@ +const requestAndRespondSignUpChallenge = async () => { + let challengeResponse; + try { + challengeResponse = await fetch('/api/signup/nostr-challenge', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + const { challenge } = await challengeResponse.json(); + + let pubkey; + try { + pubkey = await window.nostr.getPublicKey(); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + const event = { + kind: 22242, + created_at: Math.floor(Date.now() / 1000), + tags: [['challenge', challenge]], + content: 'Sign this challenge to authenticate', + pubkey: pubkey, + }; + + let signedEvent; + try { + signedEvent = await window.nostr.signEvent(event); + } catch (error) { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + return; + } + + let verifyResponse; + try { + verifyResponse = await fetch('/api/signup/nostr-verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signedEvent), + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + return verifyResponse; +}; + +module.exports = { + requestAndRespondSignUpChallenge, +}; diff --git a/src/views/invite.ejs b/src/views/invite.ejs index d7d8f79..5319a26 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -21,21 +21,8 @@ />

Usa tu extensión de Nostr para darte de alta:

-
- +
+
Date: Fri, 14 Mar 2025 16:14:01 +0100 Subject: [PATCH 213/239] warnings and stuff --- src/front/components/NostrSignUpButton.js | 0 src/front/components/NostrSignupButton.js | 42 ++++++++ src/front/pages/invite.js | 126 ++++++++++++++-------- src/views/invite.ejs | 48 +-------- 4 files changed, 128 insertions(+), 88 deletions(-) delete mode 100644 src/front/components/NostrSignUpButton.js create mode 100644 src/front/components/NostrSignupButton.js diff --git a/src/front/components/NostrSignUpButton.js b/src/front/components/NostrSignUpButton.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/front/components/NostrSignupButton.js b/src/front/components/NostrSignupButton.js new file mode 100644 index 0000000..8075a40 --- /dev/null +++ b/src/front/components/NostrSignupButton.js @@ -0,0 +1,42 @@ +class NostrSignupButton { + constructor({ parentElement, id, onClickCallback }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.onClickCallback = onClickCallback; + } + + render() { + const thisButton = document.createElement('button'); + thisButton.id = this.id; + thisButton.type = 'submit'; + thisButton.className = 'button-large button-nostr'; + + const figure = document.createElement('figure'); + + const img = document.createElement('img'); + img.src = '/img/white_ostrich.svg'; + img.style.width = '40%'; + img.style.margin = '-5% -5%'; + + figure.appendChild(img); + + const paragraph = document.createElement('p'); + paragraph.textContent = 'Alta con Nostr'; + + thisButton.appendChild(figure); + thisButton.appendChild(paragraph); + + thisButton.addEventListener('click', () => { + this.onClickCallback(); + }); + + this.element = thisButton; + this.parentElement.appendChild(this.element); + } + + disable() { + this.element.disabled = true; + } +} +module.exports = NostrSignupButton; diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index d8f1672..22e2a82 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -1,53 +1,38 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); const signupService = require('../services/signupService'); const constants = require('../../constants'); +const NostrSignupButton = require('../components/NostrSignupButton'); -class NostrSignupButton { - constructor({ parentElement, id, onClickCallback }) { - this.element = null; - this.parentElement = parentElement; - this.id = id; - this.onClickCallback = onClickCallback; - } - - render() { - const thisButton = document.createElement('button'); - thisButton.id = this.id; - thisButton.type = 'submit'; - thisButton.className = 'button-large button-nostr'; - - const figure = document.createElement('figure'); - - const img = document.createElement('img'); - img.src = '/img/white_ostrich.svg'; - img.style.width = '40%'; - img.style.margin = '-5% -5%'; - - figure.appendChild(img); - - const paragraph = document.createElement('p'); - paragraph.textContent = 'Alta con Nostr'; - - thisButton.appendChild(figure); - thisButton.appendChild(paragraph); - - thisButton.addEventListener('click', () => { - this.onClickCallback(); - }); - - this.element = thisButton; - this.parentElement.appendChild(this.element); - } - - disable() { - this.element.disabled = true; - } -} - +const warningsContainer = document.getElementById('warnings-container'); const nostrSignupArea = document.getElementById('nostr-signup-area'); const noExtensionNudges = document.getElementById('no-extension-nudges'); const signUpSuccessNotification = document.getElementById('sign-up-success'); +class WarningDiv { + constructor({ parentElement, id, innerHTML }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.innerHTML = innerHTML; + } + + render() { + const div = document.createElement('div'); + div.id = this.id; + div.className = 'card-secondary'; + div.style.display = 'none'; + + div.innerHTML = this.innerHTML; + + this.element = div; + this.parentElement.appendChild(div); + } + + display() { + this.element.style.display = 'block'; + } +} + const invitesFunction = () => { const signupButton = new NostrSignupButton({ parentElement: nostrSignupArea, @@ -58,6 +43,61 @@ const invitesFunction = () => { }); signupButton.render(); + const noExtensionWarning = new WarningDiv({ + parentElement: warningsContainer, + id: 'no-extension-nudges', + innerHTML: `

+ ¡Atención! No se ha encontrado una extensión de Nostr en tu + navegador. Puedes usar: +

+

Firefox

+

+ Alby +

+

+ nos2x-fox +

+

Chrome

+

+ Alby +

+

+ nos2x +

`, + }); + noExtensionWarning.render(); + + const rejectedNostrWarning = new WarningDiv({ + parentElement: warningsContainer, + id: 'rejected-nostr-nudges', + innerHTML: `

+ Ups, parece que no has aceptado que usemos tus claves. Si te has + equivocado, puedes intentarlo de nuevo. +

`, + }); + + rejectedNostrWarning.render(); + window.onload = () => { checkNostrExtension( window, @@ -67,7 +107,7 @@ const invitesFunction = () => { () => { console.log('Nostr extension not present'); signupButton.disable(); - noExtensionNudges.style.display = 'block'; + noExtensionWarning.display(); } ); }; diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 5319a26..06c71e1 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -22,8 +22,8 @@

Usa tu extensión de Nostr para darte de alta:

-
+
- + +

From 4c28cebdcef5ef1d1a2daef4e9f15930c9da626f Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 14 Mar 2025 16:24:53 +0100 Subject: [PATCH 214/239] oportunistic linting stuff --- .eslintignore | 1 + .../20250308000000-first-schema-tables.js | 2 - .../20250308000001-first-schema-fks.js | 220 +++++++++++------- src/front/pages/invite.js | 10 +- src/front/services/signupService.js | 6 +- src/views/invite.ejs | 17 +- 6 files changed, 143 insertions(+), 113 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..906a333 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +public/javascript/* \ No newline at end of file diff --git a/src/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js index c22bb65..3fb5cb9 100644 --- a/src/database/migrations/20250308000000-first-schema-tables.js +++ b/src/database/migrations/20250308000000-first-schema-tables.js @@ -1,7 +1,5 @@ 'use strict'; -const { query } = require('express'); - module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.sequelize.transaction((t) => { diff --git a/src/database/migrations/20250308000001-first-schema-fks.js b/src/database/migrations/20250308000001-first-schema-fks.js index c79cad7..2052b0a 100644 --- a/src/database/migrations/20250308000001-first-schema-fks.js +++ b/src/database/migrations/20250308000001-first-schema-fks.js @@ -3,106 +3,146 @@ module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.sequelize.transaction((t) => { return Promise.all([ - queryInterface.addConstraint('login_challenge_created', { - fields: ['nostr_challenge_uuid'], - type: 'foreign key', - references: { - table: 'nostr_challenge_created', - field: 'uuid', + queryInterface.addConstraint( + 'login_challenge_created', + { + fields: ['nostr_challenge_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('nostr_challenge_completed', { - fields: ['challenge'], - type: 'foreign key', - references: { - table: 'nostr_challenge_created', - field: 'challenge', + { transaction: t } + ), + queryInterface.addConstraint( + 'nostr_challenge_completed', + { + fields: ['challenge'], + type: 'foreign key', + references: { + table: 'nostr_challenge_created', + field: 'challenge', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('login_challenge_completed', { - fields: ['nostr_challenge_completed_uuid'], - type: 'foreign key', - references: { - table: 'nostr_challenge_completed', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'login_challenge_completed', + { + fields: ['nostr_challenge_completed_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_completed', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('offer_deleted', { - fields: ['offer_uuid'], - type: 'foreign key', - references: { - table: 'offer_created', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'offer_deleted', + { + fields: ['offer_uuid'], + type: 'foreign key', + references: { + table: 'offer_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('offer_details_set', { - fields: ['offer_uuid'], - type: 'foreign key', - references: { - table: 'offer_created', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'offer_details_set', + { + fields: ['offer_uuid'], + type: 'foreign key', + references: { + table: 'offer_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('session_related_to_public_key', { - fields: ['session_uuid'], - type: 'foreign key', - references: { - table: 'session_created', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'session_related_to_public_key', + { + fields: ['session_uuid'], + type: 'foreign key', + references: { + table: 'session_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('sign_up_challenge_created', { - fields: ['nostr_challenge_uuid'], - type: 'foreign key', - references: { - table: 'nostr_challenge_created', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'sign_up_challenge_created', + { + fields: ['nostr_challenge_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('sign_up_challenge_created', { - fields: ['app_invite_uuid'], - type: 'foreign key', - references: { - table: 'app_invite_created', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'sign_up_challenge_created', + { + fields: ['app_invite_uuid'], + type: 'foreign key', + references: { + table: 'app_invite_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('sign_up_challenge_completed', { - fields: ['nostr_challenge_completed_uuid'], - type: 'foreign key', - references: { - table: 'nostr_challenge_completed', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'sign_up_challenge_completed', + { + fields: ['nostr_challenge_completed_uuid'], + type: 'foreign key', + references: { + table: 'nostr_challenge_completed', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), - queryInterface.addConstraint('sign_up_challenge_completed', { - fields: ['app_invite_uuid'], - type: 'foreign key', - references: { - table: 'app_invite_created', - field: 'uuid', + { transaction: t } + ), + queryInterface.addConstraint( + 'sign_up_challenge_completed', + { + fields: ['app_invite_uuid'], + type: 'foreign key', + references: { + table: 'app_invite_created', + field: 'uuid', + }, + onDelete: 'cascade', + onUpdate: 'cascade', }, - onDelete: 'cascade', - onUpdate: 'cascade', - }), + { transaction: t } + ), ]); }); }, diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index 22e2a82..5a7678c 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -5,7 +5,6 @@ const NostrSignupButton = require('../components/NostrSignupButton'); const warningsContainer = document.getElementById('warnings-container'); const nostrSignupArea = document.getElementById('nostr-signup-area'); -const noExtensionNudges = document.getElementById('no-extension-nudges'); const signUpSuccessNotification = document.getElementById('sign-up-success'); class WarningDiv { @@ -120,8 +119,13 @@ const invitesFunction = () => { } async function acceptInvite() { - const verifyResponse = - await signupService.requestAndRespondSignUpChallenge(); + const verifyResponse = await signupService.requestAndRespondSignUpChallenge( + { + onNostrErrorCallback: () => { + rejectedNostrWarning.display(); + }, + } + ); if (verifyResponse.ok) { showConfirmationAndRedirect(); diff --git a/src/front/services/signupService.js b/src/front/services/signupService.js index 03f2469..d494c6c 100644 --- a/src/front/services/signupService.js +++ b/src/front/services/signupService.js @@ -1,4 +1,4 @@ -const requestAndRespondSignUpChallenge = async () => { +const requestAndRespondSignUpChallenge = async ({ onNostrErrorCallback }) => { let challengeResponse; try { challengeResponse = await fetch('/api/signup/nostr-challenge', { @@ -18,7 +18,7 @@ const requestAndRespondSignUpChallenge = async () => { try { pubkey = await window.nostr.getPublicKey(); } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + onNostrErrorCallback(); return; } const event = { @@ -33,7 +33,7 @@ const requestAndRespondSignUpChallenge = async () => { try { signedEvent = await window.nostr.signEvent(event); } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + onNostrErrorCallback(); return; } diff --git a/src/views/invite.ejs b/src/views/invite.ejs index 06c71e1..ad4a406 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -21,21 +21,8 @@ />

Usa tu extensión de Nostr para darte de alta:

-
-
-
- - -
+
+

From 29a4c0ca694451a98dc0d88c78bf0aa5fb9d8652 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 14 Mar 2025 16:57:55 +0100 Subject: [PATCH 215/239] popup component --- src/front/components/PopupNotification.js | 25 +++++++++++++ src/front/components/WarningDiv.js | 25 +++++++++++++ src/front/pages/invite.js | 45 +++++++---------------- src/views/invite.ejs | 8 +--- 4 files changed, 65 insertions(+), 38 deletions(-) create mode 100644 src/front/components/PopupNotification.js create mode 100644 src/front/components/WarningDiv.js diff --git a/src/front/components/PopupNotification.js b/src/front/components/PopupNotification.js new file mode 100644 index 0000000..e2a0588 --- /dev/null +++ b/src/front/components/PopupNotification.js @@ -0,0 +1,25 @@ +class PopupNotification { + constructor({ parentElement, id, text }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.text = text; + } + + render() { + const div = document.createElement('div'); + div.id = this.id; + div.className = 'top-notification-good'; + + div.innerHTML = ` +

${this.text}

`; + + this.element = div; + this.parentElement.appendChild(div); + } + + display() { + this.element.classList.add('revealed'); + } +} +module.exports = PopupNotification; diff --git a/src/front/components/WarningDiv.js b/src/front/components/WarningDiv.js new file mode 100644 index 0000000..ab4fa93 --- /dev/null +++ b/src/front/components/WarningDiv.js @@ -0,0 +1,25 @@ +class WarningDiv { + constructor({ parentElement, id, innerHTML }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.innerHTML = innerHTML; + } + + render() { + const div = document.createElement('div'); + div.id = this.id; + div.className = 'card-secondary'; + div.style.display = 'none'; + + div.innerHTML = this.innerHTML; + + this.element = div; + this.parentElement.appendChild(div); + } + + display() { + this.element.style.display = 'block'; + } +} +module.exports = WarningDiv; diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index 5a7678c..e67ca9c 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -2,37 +2,14 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); const signupService = require('../services/signupService'); const constants = require('../../constants'); const NostrSignupButton = require('../components/NostrSignupButton'); - -const warningsContainer = document.getElementById('warnings-container'); -const nostrSignupArea = document.getElementById('nostr-signup-area'); -const signUpSuccessNotification = document.getElementById('sign-up-success'); - -class WarningDiv { - constructor({ parentElement, id, innerHTML }) { - this.element = null; - this.parentElement = parentElement; - this.id = id; - this.innerHTML = innerHTML; - } - - render() { - const div = document.createElement('div'); - div.id = this.id; - div.className = 'card-secondary'; - div.style.display = 'none'; - - div.innerHTML = this.innerHTML; - - this.element = div; - this.parentElement.appendChild(div); - } - - display() { - this.element.style.display = 'block'; - } -} +const WarningDiv = require('../components/WarningDiv'); +const PopupNotification = require('../components/PopupNotification'); const invitesFunction = () => { + const body = document.querySelector('body'); + const warningsContainer = document.getElementById('warnings-container'); + const nostrSignupArea = document.getElementById('nostr-signup-area'); + const signupButton = new NostrSignupButton({ parentElement: nostrSignupArea, id: 'nostr-signup-button', @@ -94,9 +71,15 @@ const invitesFunction = () => { equivocado, puedes intentarlo de nuevo.

`, }); - rejectedNostrWarning.render(); + const signUpSuccessPopup = new PopupNotification({ + parentElement: body, + id: 'sign-up-success', + text: '¡Bien! Hemos dado de alta tu clave de Nostr. Te vamos a redirigir a la seca, espera un momento.', + }); + signUpSuccessPopup.render(); + window.onload = () => { checkNostrExtension( window, @@ -112,7 +95,7 @@ const invitesFunction = () => { }; function showConfirmationAndRedirect() { - signUpSuccessNotification.classList.add('revealed'); + signUpSuccessPopup.display(); setTimeout(() => { window.location.href = constants.API_PATHS.createProfile; }, constants.DEFAULT_REDIRECT_DELAY); diff --git a/src/views/invite.ejs b/src/views/invite.ejs index ad4a406..d6ddaea 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -23,13 +23,7 @@

Usa tu extensión de Nostr para darte de alta:

-
- -

- ¡Bien! Hemos dado de alta tu clave de Nostr. Te vamos a redirigir - a la seca, espera un momento. -

-
+

¿No tienes cuenta de Nostr?

Date: Fri, 14 Mar 2025 17:03:29 +0100 Subject: [PATCH 216/239] inline useless functions --- src/front/pages/invite.js | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index e67ca9c..f79e7e6 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -13,8 +13,20 @@ const invitesFunction = () => { const signupButton = new NostrSignupButton({ parentElement: nostrSignupArea, id: 'nostr-signup-button', - onClickCallback: () => { - acceptInvite(); + onClickCallback: async () => { + const verifyResponse = + await signupService.requestAndRespondSignUpChallenge({ + onNostrErrorCallback: () => { + rejectedNostrWarning.display(); + }, + }); + + if (verifyResponse.ok) { + signUpSuccessPopup.display(); + setTimeout(() => { + window.location.href = constants.API_PATHS.createProfile; + }, constants.DEFAULT_REDIRECT_DELAY); + } }, }); signupButton.render(); @@ -93,27 +105,6 @@ const invitesFunction = () => { } ); }; - - function showConfirmationAndRedirect() { - signUpSuccessPopup.display(); - setTimeout(() => { - window.location.href = constants.API_PATHS.createProfile; - }, constants.DEFAULT_REDIRECT_DELAY); - } - - async function acceptInvite() { - const verifyResponse = await signupService.requestAndRespondSignUpChallenge( - { - onNostrErrorCallback: () => { - rejectedNostrWarning.display(); - }, - } - ); - - if (verifyResponse.ok) { - showConfirmationAndRedirect(); - } - } }; invitesFunction(); From 8ae4fddd12e0cde929484b15c9f77a407315194c Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 14 Mar 2025 18:28:53 +0100 Subject: [PATCH 217/239] refactor name --- src/front/pages/invite.js | 4 ++-- src/front/services/{signupService.js => inviteService.js} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/front/services/{signupService.js => inviteService.js} (100%) diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index f79e7e6..d795db7 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -1,5 +1,5 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); -const signupService = require('../services/signupService'); +const inviteService = require('../services/inviteService'); const constants = require('../../constants'); const NostrSignupButton = require('../components/NostrSignupButton'); const WarningDiv = require('../components/WarningDiv'); @@ -15,7 +15,7 @@ const invitesFunction = () => { id: 'nostr-signup-button', onClickCallback: async () => { const verifyResponse = - await signupService.requestAndRespondSignUpChallenge({ + await inviteService.requestAndRespondSignUpChallenge({ onNostrErrorCallback: () => { rejectedNostrWarning.display(); }, diff --git a/src/front/services/signupService.js b/src/front/services/inviteService.js similarity index 100% rename from src/front/services/signupService.js rename to src/front/services/inviteService.js From add7891e94a23d2d01fe845aeaafeef185d9b641 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 14 Mar 2025 18:52:00 +0100 Subject: [PATCH 218/239] login service --- src/front/pages/login.js | 60 +++++---------------------- src/front/services/loginService.js | 65 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 49 deletions(-) create mode 100644 src/front/services/loginService.js diff --git a/src/front/pages/login.js b/src/front/pages/login.js index 752c376..cf5fb92 100644 --- a/src/front/pages/login.js +++ b/src/front/pages/login.js @@ -1,6 +1,9 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); +const loginService = require('../services/loginService'); const loginsFunction = () => { + const rejectedNostrWarning = document.querySelector('#rejected-nostr-nudges'); + window.onload = () => { checkNostrExtension( window, @@ -16,55 +19,14 @@ const loginsFunction = () => { }; async function login() { - let challengeResponse; - try { - challengeResponse = await fetch('/api/login/nostr-challenge', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } - - const { challenge } = await challengeResponse.json(); - - let pubkey; - try { - pubkey = await window.nostr.getPublicKey(); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - const event = { - kind: 22242, - created_at: Math.floor(Date.now() / 1000), - tags: [['challenge', challenge]], - content: 'Sign this challenge to authenticate', - pubkey: pubkey, - }; - - let signedEvent; - try { - signedEvent = await window.nostr.signEvent(event); - } catch (error) { - document.querySelector('#rejected-nostr-nudges').style.display = 'block'; - return; - } - - let verifyResponse; - try { - verifyResponse = await fetch('/api/login/nostr-verify', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signedEvent), - }); - } catch (error) { - console.log(`Something went wrong: ${error}`); - return; - } + const verifyResponse = await loginService.requestAndRespondLoginChallenge({ + onRejectedPubKeyCallback: () => { + rejectedNostrWarning.style.display = 'block'; + }, + onRejectedSignatureCallback: () => { + rejectedNostrWarning.style.display = 'block'; + }, + }); if (verifyResponse.status === 403) { document.querySelector('#rejected-public-key').style.display = 'block'; diff --git a/src/front/services/loginService.js b/src/front/services/loginService.js new file mode 100644 index 0000000..d1f5815 --- /dev/null +++ b/src/front/services/loginService.js @@ -0,0 +1,65 @@ +const requestAndRespondLoginChallenge = async ({ + onRejectedPubKeyCallback, + onRejectedSignatureCallback, +}) => { + onRejectedPubKeyCallback = () => { + document.querySelector('#rejected-nostr-nudges').style.display = 'block'; + }; + onRejectedSignatureCallback = onRejectedPubKeyCallback; + + let challengeResponse; + try { + challengeResponse = await fetch('/api/login/nostr-challenge', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + const { challenge } = await challengeResponse.json(); + + let pubkey; + try { + pubkey = await window.nostr.getPublicKey(); + } catch (error) { + onRejectedPubKeyCallback(); + return; + } + const event = { + kind: 22242, + created_at: Math.floor(Date.now() / 1000), + tags: [['challenge', challenge]], + content: 'Sign this challenge to authenticate', + pubkey: pubkey, + }; + + let signedEvent; + try { + signedEvent = await window.nostr.signEvent(event); + } catch (error) { + onRejectedSignatureCallback(); + return; + } + + let verifyResponse; + try { + verifyResponse = await fetch('/api/login/nostr-verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(signedEvent), + }); + } catch (error) { + console.log(`Something went wrong: ${error}`); + return; + } + + return verifyResponse; +}; + +module.exports = { + requestAndRespondLoginChallenge, +}; From 4006523c8cec433367aa72b8ece1152124574e3f Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 12:46:55 +0100 Subject: [PATCH 219/239] lots of stuff --- src/constants.js | 1 + .../{NostrSignupButton.js => nostrButtons.js} | 31 +++- src/front/pages/invite.js | 12 +- src/front/pages/login.js | 150 +++++++++++++----- src/front/utils/checkNostrExtension.js | 2 +- src/views/login.ejs | 76 +-------- 6 files changed, 152 insertions(+), 120 deletions(-) rename src/front/components/{NostrSignupButton.js => nostrButtons.js} (59%) diff --git a/src/constants.js b/src/constants.js index 79c5699..7af448b 100644 --- a/src/constants.js +++ b/src/constants.js @@ -4,6 +4,7 @@ const DEFAULT_REDIRECT_DELAY = 3 * 1000; // 3seconds times milliseconds; const API_PATHS = { createProfile: '/createProfile', + home: '/home', }; module.exports = { diff --git a/src/front/components/NostrSignupButton.js b/src/front/components/nostrButtons.js similarity index 59% rename from src/front/components/NostrSignupButton.js rename to src/front/components/nostrButtons.js index 8075a40..31513f5 100644 --- a/src/front/components/NostrSignupButton.js +++ b/src/front/components/nostrButtons.js @@ -1,9 +1,10 @@ -class NostrSignupButton { - constructor({ parentElement, id, onClickCallback }) { +class NostrButton { + constructor({ parentElement, id, onClickCallback, buttonText }) { this.element = null; this.parentElement = parentElement; this.id = id; this.onClickCallback = onClickCallback; + this.buttonText = buttonText; } render() { @@ -22,7 +23,7 @@ class NostrSignupButton { figure.appendChild(img); const paragraph = document.createElement('p'); - paragraph.textContent = 'Alta con Nostr'; + paragraph.textContent = this.buttonText; thisButton.appendChild(figure); thisButton.appendChild(paragraph); @@ -36,7 +37,27 @@ class NostrSignupButton { } disable() { - this.element.disabled = true; + if (this.element) { + this.element.disabled = true; + } } } -module.exports = NostrSignupButton; + +class NostrSignupButton extends NostrButton { + constructor({ parentElement, id, onClickCallback }) { + super({ parentElement, id, onClickCallback, buttonText: 'Alta con Nostr' }); + } +} + +class NostrLoginButton extends NostrButton { + constructor({ parentElement, id, onClickCallback }) { + super({ + parentElement, + id, + onClickCallback, + buttonText: 'Login con Nostr', + }); + } +} + +module.exports = { NostrSignupButton, NostrLoginButton }; diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index d795db7..4b58b37 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -1,7 +1,7 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); const inviteService = require('../services/inviteService'); const constants = require('../../constants'); -const NostrSignupButton = require('../components/NostrSignupButton'); +const { NostrSignupButton } = require('../components/nostrButtons'); const WarningDiv = require('../components/WarningDiv'); const PopupNotification = require('../components/PopupNotification'); @@ -93,17 +93,17 @@ const invitesFunction = () => { signUpSuccessPopup.render(); window.onload = () => { - checkNostrExtension( + checkNostrExtension({ window, - () => { + successCallback: () => { console.log('Nostr extension present'); }, - () => { + : () => { console.log('Nostr extension not present'); signupButton.disable(); noExtensionWarning.display(); - } - ); + }, + }); }; }; diff --git a/src/front/pages/login.js b/src/front/pages/login.js index cf5fb92..5620a99 100644 --- a/src/front/pages/login.js +++ b/src/front/pages/login.js @@ -1,49 +1,127 @@ const checkNostrExtension = require('../utils/checkNostrExtension'); const loginService = require('../services/loginService'); +const WarningDiv = require('../components/WarningDiv'); +const { NostrLoginButton } = require('../components/nostrButtons'); +const PopupNotification = require('../components/PopupNotification'); +const constants = require('../../constants'); -const loginsFunction = () => { - const rejectedNostrWarning = document.querySelector('#rejected-nostr-nudges'); +const loginFunction = () => { + const loginButtonArea = document.getElementById('login-button-area'); + const warningsArea = document.getElementById('warnings-area'); + + const successPopup = new PopupNotification({ + parentElement: document.querySelector('body'), + id: 'login-success-popup', + text: '¡Éxito! Te estamos llevando a la app...', + }); + successPopup.render(); + const nostrLoginButton = new NostrLoginButton({ + parentElement: loginButtonArea, + id: 'login-button', + onClickCallback: async () => { + const verifyResponse = await loginService.requestAndRespondLoginChallenge( + { + onRejectedPubKeyCallback: () => { + nostrRejectedWarning.display(); + }, + onRejectedSignatureCallback: () => { + nostrRejectedWarning.display(); + }, + } + ); + + if (verifyResponse.status === 403) { + notRegisteredPubkeyWarning.display(); + } + + if (verifyResponse.ok) { + nostrLoginButton.disable(); + successPopup.display(); + setTimeout(() => { + window.location.href = constants.API_PATHS.home; + }, constants.DEFAULT_REDIRECT_DELAY); + } + }, + }); + nostrLoginButton.render(); + + const notRegisteredPubkeyWarning = new WarningDiv({ + parentElement: warningsArea, + id: 'rejected-public-key', + innerHTML: `

+ Ups, esa clave no está registrada en la seca. ¿Quizás estás usando un + perfil equivocado? +

`, + }); + notRegisteredPubkeyWarning.render(); + + const noExtensionWarning = new WarningDiv({ + parentElement: warningsArea, + id: 'no-extension-nudges', + innerHTML: `

+ ¡Atención! No se ha encontrado una extensión de Nostr en tu navegador. + Puedes usar: +

+

Firefox

+

+ Alby +

+

+ nos2x-fox +

+

Chrome

+

+ Alby +

+

+ nos2x +

`, + }); + noExtensionWarning.render(); + + const nostrRejectedWarning = new WarningDiv({ + parentElement: warningsArea, + id: 'rejected-nostr-nudges', + innerHTML: `

+ Ups, parece que no has aceptado que usemos tus claves. Si te has + equivocado, puedes intentarlo de nuevo. +

`, + }); + nostrRejectedWarning.render(); window.onload = () => { - checkNostrExtension( + checkNostrExtension({ window, - () => { + successCallback: () => { console.log('Nostr extension present'); }, - () => { + failureCallback: () => { console.log('Nostr extension not present'); - document.querySelector('#login-button').disabled = true; - document.querySelector('#no-extension-nudges').style.display = 'block'; - } - ); - }; - - async function login() { - const verifyResponse = await loginService.requestAndRespondLoginChallenge({ - onRejectedPubKeyCallback: () => { - rejectedNostrWarning.style.display = 'block'; - }, - onRejectedSignatureCallback: () => { - rejectedNostrWarning.style.display = 'block'; + nostrLoginButton.disable(); + noExtensionWarning.display(); }, }); - - if (verifyResponse.status === 403) { - document.querySelector('#rejected-public-key').style.display = 'block'; - } - - if (verifyResponse.ok) { - document.querySelector('#sign-up-success').style.display = 'block'; - setTimeout(() => { - window.location.href = '/home'; - }, 1000); - } - } - - const loginButton = document.getElementById('login-button'); - loginButton.addEventListener('click', () => { - login(); - }); + }; }; -loginsFunction(); +loginFunction(); diff --git a/src/front/utils/checkNostrExtension.js b/src/front/utils/checkNostrExtension.js index b1d9610..b16d293 100644 --- a/src/front/utils/checkNostrExtension.js +++ b/src/front/utils/checkNostrExtension.js @@ -1,4 +1,4 @@ -function checkNostrExtension(window, successCallback, failureCallback) { +function checkNostrExtension({ window, successCallback, failureCallback }) { if (!window.nostr) { failureCallback(); } else { diff --git a/src/views/login.ejs b/src/views/login.ejs index a954697..2cf9dcb 100644 --- a/src/views/login.ejs +++ b/src/views/login.ejs @@ -1,7 +1,7 @@ - Hello World + @@ -13,78 +13,10 @@
-
- -
- - - - From 2b7b761737f2d37018268e370b032b706edc5bde Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 12:53:44 +0100 Subject: [PATCH 220/239] styles and typo --- public/css/login.css | 39 +++++++++++++++++++++++++++++++++++++++ src/front/pages/invite.js | 2 +- src/views/login.ejs | 4 +++- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 public/css/login.css diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 0000000..f457888 --- /dev/null +++ b/public/css/login.css @@ -0,0 +1,39 @@ +@media (max-width: 768px) { + #login-button { + width: 200px; + } + + .logo { + width: 350px; + } + + #login-card-content { + width: 100%; + } +} + +@media (min-width: 769px) { + #login-button { + width: 300px; + } + + .logo { + width: 500px; + } + + #login-card-content { + width: 40%; + min-width: min-content; + } +} + +#login-card-content { + margin-right: auto; + margin-left: auto; + padding: 20px; +} + +#login-card-content > * { + margin: 1vh auto; + text-align: center; +} diff --git a/src/front/pages/invite.js b/src/front/pages/invite.js index 4b58b37..24bc3b5 100644 --- a/src/front/pages/invite.js +++ b/src/front/pages/invite.js @@ -98,7 +98,7 @@ const invitesFunction = () => { successCallback: () => { console.log('Nostr extension present'); }, - : () => { + failureCallback: () => { console.log('Nostr extension not present'); signupButton.disable(); noExtensionWarning.display(); diff --git a/src/views/login.ejs b/src/views/login.ejs index 2cf9dcb..d2870e6 100644 --- a/src/views/login.ejs +++ b/src/views/login.ejs @@ -5,11 +5,12 @@ +
-

Bienvenido a la seca

+
@@ -18,6 +19,7 @@
+
From 4ee00edb0449f942c63a05f999300e1fb95badd5 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 14:56:51 +0100 Subject: [PATCH 221/239] extract out function --- src/front/pages/offers.js | 6 ++---- src/front/utils/formatNumbersWithSpaces.js | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 src/front/utils/formatNumbersWithSpaces.js diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index b0954d3..b7a271c 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,8 +1,6 @@ -function offersPage() { - function formatNumberWithSpaces(num) { - return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); - } +const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces'); +function offersPage() { const navbuttonHome = document.getElementById('navbutton-home'); const navbuttonOffers = document.getElementById('navbutton-offers'); diff --git a/src/front/utils/formatNumbersWithSpaces.js b/src/front/utils/formatNumbersWithSpaces.js new file mode 100644 index 0000000..1d1ee44 --- /dev/null +++ b/src/front/utils/formatNumbersWithSpaces.js @@ -0,0 +1,5 @@ +function formatNumberWithSpaces(num) { + return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); +} + +module.exports = formatNumberWithSpaces; From c82fc895b726dbc5fc202d0e380306381449cab4 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 15:26:57 +0100 Subject: [PATCH 222/239] button extracted --- src/front/pages/offers.js | 40 +++++++++++++++++++++++++++++++-------- src/views/offers.ejs | 7 ------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index b7a271c..54d72a5 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,6 +1,38 @@ const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces'); +class PublishOfferButton { + constructor({ parentElement, id, onClickCallback }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.onClickCallback = onClickCallback; + } + + render() { + const button = document.createElement('button'); + button.id = this.id; + button.className = 'button-primary button-large'; + button.innerText = 'Publicar oferta'; + button.addEventListener('click', this.onClickCallback); + + this.element = button; + this.parentElement.appendChild(this.element); + } +} + function offersPage() { + const publishOfferButton = new PublishOfferButton({ + parentElement: document.getElementById('submit-button-area'), + id: 'button-submit-offer', + onClickCallback: async () => { + await publishOffer(); + await myOffers.getOffersFromApi(); + await myOffers.render(); + }, + }); + publishOfferButton.render(); + + // ----------- const navbuttonHome = document.getElementById('navbutton-home'); const navbuttonOffers = document.getElementById('navbutton-offers'); @@ -58,8 +90,6 @@ function offersPage() { 'large-bills-checkbox' ); - const publishOfferButton = document.getElementById('button-submit-offer'); - const offerCreatedPopup = document.getElementById( 'offer-created-confirmation' ); @@ -696,12 +726,6 @@ function offersPage() { applyTrustCheckboxConstraints(allMembersCheckbox); }); - publishOfferButton.addEventListener('click', async () => { - await publishOffer(); - await myOffers.getOffersFromApi(); - await myOffers.render(); - }); - updateBtcInput(); const myOffers = new MyOffers(ownOffersContainer); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 2c99fe7..d492002 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -179,12 +179,6 @@
-
- <%- include("partials/appCommonScripts") %> From 47c50ad07834f4970a0438c14bf9e9dbfd0aed07 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 15:28:44 +0100 Subject: [PATCH 223/239] move to different file --- src/front/components/PublishOfferButton.js | 21 +++++++++++++++++++++ src/front/pages/offers.js | 21 +-------------------- 2 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 src/front/components/PublishOfferButton.js diff --git a/src/front/components/PublishOfferButton.js b/src/front/components/PublishOfferButton.js new file mode 100644 index 0000000..d43f8c1 --- /dev/null +++ b/src/front/components/PublishOfferButton.js @@ -0,0 +1,21 @@ +class PublishOfferButton { + constructor({ parentElement, id, onClickCallback }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + this.onClickCallback = onClickCallback; + } + + render() { + const button = document.createElement('button'); + button.id = this.id; + button.className = 'button-primary button-large'; + button.innerText = 'Publicar oferta'; + button.addEventListener('click', this.onClickCallback); + + this.element = button; + this.parentElement.appendChild(this.element); + } +} + +module.exports = PublishOfferButton; diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index 54d72a5..65a848c 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,24 +1,5 @@ const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces'); - -class PublishOfferButton { - constructor({ parentElement, id, onClickCallback }) { - this.element = null; - this.parentElement = parentElement; - this.id = id; - this.onClickCallback = onClickCallback; - } - - render() { - const button = document.createElement('button'); - button.id = this.id; - button.className = 'button-primary button-large'; - button.innerText = 'Publicar oferta'; - button.addEventListener('click', this.onClickCallback); - - this.element = button; - this.parentElement.appendChild(this.element); - } -} +const PublishOfferButton = require('../components/PublishOfferButton'); function offersPage() { const publishOfferButton = new PublishOfferButton({ From ceba684d77f10e179c4eb7ffba052ff34491be69 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 16:36:31 +0100 Subject: [PATCH 224/239] buy or sell button group --- src/front/components/BuyOrSellButtonGroup.js | 56 ++++++++++++++++++++ src/front/pages/offers.js | 37 +++---------- src/views/offers.ejs | 11 ---- 3 files changed, 64 insertions(+), 40 deletions(-) create mode 100644 src/front/components/BuyOrSellButtonGroup.js diff --git a/src/front/components/BuyOrSellButtonGroup.js b/src/front/components/BuyOrSellButtonGroup.js new file mode 100644 index 0000000..ed03c73 --- /dev/null +++ b/src/front/components/BuyOrSellButtonGroup.js @@ -0,0 +1,56 @@ +class BuyOrSellButtonGroup { + constructor({ parentElement, id }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + + this.buyButton = null; + this.sellButton = null; + } + + render() { + const groupDiv = document.createElement('div'); + groupDiv.className = 'button-group'; + groupDiv.id = this.id; + + const buyButton = document.createElement('button'); + buyButton.dataset.value = 'buy-bitcoin'; + buyButton.id = 'button-buy-bitcoin'; + buyButton.className = 'selected'; + buyButton.textContent = 'Quiero comprar Bitcoin'; + this.buyButton = buyButton; + + const sellButton = document.createElement('button'); + sellButton.dataset.value = 'sell-bitcoin'; + sellButton.id = 'button-sell-bitcoin'; + sellButton.textContent = 'Quiero vender Bitcoin'; + this.sellButton = sellButton; + + groupDiv.appendChild(this.buyButton); + groupDiv.appendChild(this.sellButton); + + for (const button of [this.buyButton, this.sellButton]) { + button.addEventListener('click', () => { + [this.buyButton, this.sellButton].forEach((aButton) => { + if (aButton.classList.contains('selected')) { + aButton.classList.remove('selected'); + } else { + aButton.classList.add('selected'); + } + }); + }); + } + + this.element = groupDiv; + this.parentElement.appendChild(this.element); + } + + wants() { + if (this.buyButton.classList.contains('selected')) { + return 'BTC'; + } + if (this.sellButton.classList.contains('selected')) { + return 'EUR'; + } + } +} diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index 65a848c..b6d4e2f 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,5 +1,6 @@ const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces'); const PublishOfferButton = require('../components/PublishOfferButton'); +const BuyOrSellButtonGroup = require('../components/BuyOrSellButtonGroup'); function offersPage() { const publishOfferButton = new PublishOfferButton({ @@ -13,6 +14,12 @@ function offersPage() { }); publishOfferButton.render(); + const buyOrSellButtonGroup = new BuyOrSellButtonGroup({ + parentElement: document.getElementById('buy-or-sell-area'), + id: 'button-group-buy-or-sell', + }); + buyOrSellButtonGroup.render(); + // ----------- const navbuttonHome = document.getElementById('navbutton-home'); const navbuttonOffers = document.getElementById('navbutton-offers'); @@ -34,12 +41,6 @@ function offersPage() { 'create-offer-modal-root' ); const viewMyOffersRoot = document.getElementById('view-my-offers-root'); - const buyOrSellButtonGroup = document.getElementById( - 'button-group-buy-or-sell' - ); - const buyOrSellButtons = buyOrSellButtonGroup.querySelectorAll('button'); - const buyButton = document.getElementById('button-buy-bitcoin'); - const sellButton = document.getElementById('button-sell-bitcoin'); const premiumValue = document.getElementById('premium-value'); const buttonIncreasePremium = document.getElementById( @@ -98,16 +99,6 @@ function offersPage() { premiumValue.innerText = newValue; } - function toggleBuyOrSellButtonGroup() { - buyOrSellButtons.forEach((button) => { - if (button.classList.contains('selected')) { - button.classList.remove('selected'); - } else { - button.classList.add('selected'); - } - }); - } - function readIntFromEurAmountInput() { const eurAmountFieldValue = eurAmountInput.value; const regularExpression = /([\d\s]+)/; @@ -168,13 +159,7 @@ function offersPage() { } async function publishOffer() { - let wants; - if (buyButton.classList.contains('selected')) { - wants = 'BTC'; - } - if (sellButton.classList.contains('selected')) { - wants = 'EUR'; - } + const wants = buyOrSellButtonGroup.wants(); const premium = parseInt(premiumValue.innerText.match(/\d+/)[0]) / 100; const trade_amount_eur = eurAmountInput.value; @@ -669,12 +654,6 @@ function offersPage() { toggleCreateOfferModal(); }); - buyOrSellButtons.forEach((button) => { - button.addEventListener('click', () => { - toggleBuyOrSellButtonGroup(); - }); - }); - buttonIncreasePremium.addEventListener('click', () => { modifyPremiumValue(1); }); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index d492002..81a1664 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -36,17 +36,6 @@

Añade los detalles de tu oferta

-
- -

Premium

From 1da32520612fc54912f51f4700ea2053ff944d11 Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 16:36:50 +0100 Subject: [PATCH 225/239] missing export --- src/front/components/BuyOrSellButtonGroup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/front/components/BuyOrSellButtonGroup.js b/src/front/components/BuyOrSellButtonGroup.js index ed03c73..d563e54 100644 --- a/src/front/components/BuyOrSellButtonGroup.js +++ b/src/front/components/BuyOrSellButtonGroup.js @@ -54,3 +54,5 @@ class BuyOrSellButtonGroup { } } } + +module.exports = BuyOrSellButtonGroup; From b8579a5370b46825a15f37ce01647be129fe1d3f Mon Sep 17 00:00:00 2001 From: counterweight Date: Sat, 15 Mar 2025 17:28:48 +0100 Subject: [PATCH 226/239] premium selector --- public/css/seca.css | 2 +- src/front/components/PremiumSelector.js | 66 +++++++++++++++++++++++++ src/front/pages/offers.js | 35 +++---------- src/views/offers.ejs | 11 +---- 4 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 src/front/components/PremiumSelector.js diff --git a/public/css/seca.css b/public/css/seca.css index 23b4802..1cc3034 100644 --- a/public/css/seca.css +++ b/public/css/seca.css @@ -117,7 +117,7 @@ h1 { } .full-screen-modal-background { - position: absolute; + position: fixed; width: 100%; height: 100%; top: 0; diff --git a/src/front/components/PremiumSelector.js b/src/front/components/PremiumSelector.js new file mode 100644 index 0000000..f098f89 --- /dev/null +++ b/src/front/components/PremiumSelector.js @@ -0,0 +1,66 @@ +class PremiumSelector { + constructor({ parentElement, id }) { + this.element = null; + this.parentElement = parentElement; + this.id = id; + + this.premiumValue = null; + } + + render() { + const premiumSelectorArea = document.createElement('div'); + premiumSelectorArea.id = this.id; + + const premiumValue = document.createElement('div'); + premiumValue.id = 'premium-value'; + premiumValue.textContent = '0%'; + this.premiumValue = premiumValue; + + const premiumButtonsContainer = document.createElement('div'); + premiumButtonsContainer.id = 'premium-buttons-container'; + + const increaseButton = document.createElement('button'); + increaseButton.classList.add('premium-button'); + increaseButton.id = 'button-increase-premium'; + increaseButton.textContent = '+'; + + const decreaseButton = document.createElement('button'); + decreaseButton.classList.add('premium-button'); + decreaseButton.id = 'button-decrease-premium'; + decreaseButton.textContent = '-'; + + premiumButtonsContainer.appendChild(increaseButton); + premiumButtonsContainer.appendChild(decreaseButton); + + premiumSelectorArea.appendChild(premiumValue); + premiumSelectorArea.appendChild(premiumButtonsContainer); + + increaseButton.addEventListener('click', () => { + this.modifyPremiumValue(1); + }); + + decreaseButton.addEventListener('click', () => { + this.modifyPremiumValue(-1); + }); + + this.element = premiumSelectorArea; + this.parentElement.appendChild(this.element); + } + + modifyPremiumValue(delta) { + const regexExpression = /-*\d+/; + const numValue = parseInt( + this.premiumValue.innerText.match(regexExpression)[0] + ); + + const newValue = `${numValue + delta}%`; + + this.premiumValue.innerText = newValue; + } + + getPremium() { + return parseInt(this.premiumValue.innerText.match(/-?\d+/)[0]) / 100; + } +} + +module.exports = PremiumSelector; diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js index b6d4e2f..8fb4426 100644 --- a/src/front/pages/offers.js +++ b/src/front/pages/offers.js @@ -1,6 +1,7 @@ const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces'); const PublishOfferButton = require('../components/PublishOfferButton'); const BuyOrSellButtonGroup = require('../components/BuyOrSellButtonGroup'); +const PremiumSelector = require('../components/PremiumSelector'); function offersPage() { const publishOfferButton = new PublishOfferButton({ @@ -20,6 +21,12 @@ function offersPage() { }); buyOrSellButtonGroup.render(); + const premiumSelector = new PremiumSelector({ + parentElement: document.getElementById('premium-content-area'), + id: 'premium-selector-area', + }); + premiumSelector.render(); + // ----------- const navbuttonHome = document.getElementById('navbutton-home'); const navbuttonOffers = document.getElementById('navbutton-offers'); @@ -42,15 +49,6 @@ function offersPage() { ); const viewMyOffersRoot = document.getElementById('view-my-offers-root'); - const premiumValue = document.getElementById('premium-value'); - const buttonIncreasePremium = document.getElementById( - 'button-increase-premium' - ); - - const buttonDecreasePremium = document.getElementById( - 'button-decrease-premium' - ); - const eurAmountInput = document.getElementById('input-eur-amount'); const btcAmountInput = document.getElementById('input-btc-amount'); @@ -90,15 +88,6 @@ function offersPage() { viewMyOffersRoot.style.display === 'block' ? 'none' : 'block'; } - function modifyPremiumValue(delta) { - const regexExpression = /-*\d+/; - const numValue = parseInt(premiumValue.innerText.match(regexExpression)[0]); - - const newValue = `${numValue + delta}%`; - - premiumValue.innerText = newValue; - } - function readIntFromEurAmountInput() { const eurAmountFieldValue = eurAmountInput.value; const regularExpression = /([\d\s]+)/; @@ -161,7 +150,7 @@ function offersPage() { async function publishOffer() { const wants = buyOrSellButtonGroup.wants(); - const premium = parseInt(premiumValue.innerText.match(/\d+/)[0]) / 100; + const premium = premiumSelector.getPremium(); const trade_amount_eur = eurAmountInput.value; const location_details = placeInput.value; const time_availability_details = timeInput.value; @@ -654,14 +643,6 @@ function offersPage() { toggleCreateOfferModal(); }); - buttonIncreasePremium.addEventListener('click', () => { - modifyPremiumValue(1); - }); - - buttonDecreasePremium.addEventListener('click', () => { - modifyPremiumValue(-1); - }); - eurAmountInput.addEventListener('blur', () => { validateAndFormatEurAmountInput(); updateBtcInput(); diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 81a1664..46d9e3c 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -40,16 +40,7 @@

Premium

-
-
0%
-
- -
-
+

Tu precio: 90 000€/BTC From bc3ed21da4a83b3cc1a8e37f7e24cf90ad2a4b08 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Mon, 17 Mar 2025 16:01:02 +0100 Subject: [PATCH 227/239] a few small notes --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index de7c287..6098e02 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,16 @@ laseca is a social bitcoin to cash exchange, implemented as a webapp. * Installing + Run `npm install` + You can now start the app in a container by running `npm run start:container` (and shut it down with `npm run stop:container`). +* Building + + The front-end code gets built with webpack. You can build it anytime with `npm run build`. + + For development, it's useful to build continuously. You can run `npm run watch` and webpack will build every time you edit a monitored file. * Running + Copy the `.env.dist` file into `.env` and set any values you like. + The app will run in a single container, with a Postgres database, a caddy webserver and the nodejs app. + Note that the container doesn't come with a volume for Postgres: default behaviour is to start from scratch every time you create the container, delete everything every time you delete the container. + + You probably want to run migrations to get the database into proper state. You can do so with `npx sequelize-cli db:migrate`. + The docker image launches the nodejs app with nodemon, so changes to the code will be available immediately. + + Furthermore, since the git repository gets mounted live into the docker container, the live changes made by webpack watch mode will also be available as you work on front end files. + The Postgres database is reachable from the host, so you can use your favourite SQL client to access it. + You can format with `npm run format` and lint with `npm run lint`. From 536408482b6bb63500cd5d52fa4f534523214878 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Mon, 17 Mar 2025 16:23:12 +0100 Subject: [PATCH 228/239] formatting --- src/views/invite.ejs | 2 +- src/views/login.ejs | 12 +++++------- src/views/offers.ejs | 7 ++----- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/views/invite.ejs b/src/views/invite.ejs index d6ddaea..b7815ab 100644 --- a/src/views/invite.ejs +++ b/src/views/invite.ejs @@ -23,7 +23,7 @@

Usa tu extensión de Nostr para darte de alta:

- +

¿No tienes cuenta de Nostr?

diff --git a/src/views/offers.ejs b/src/views/offers.ejs index 46d9e3c..ef55506 100644 --- a/src/views/offers.ejs +++ b/src/views/offers.ejs @@ -35,12 +35,10 @@

Añade los detalles de tu oferta

-
-
+

Premium

-

Tu precio: 90 000€/BTC @@ -158,8 +156,7 @@ >

-
-
+