')
+ .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 0767123..610c098 100644
--- a/src/commands/createAppInvite.js
+++ b/src/commands/createAppInvite.js
@@ -1,7 +1,17 @@
-const invitesService = require('../services/invitesService');
+class CreateAppInviteProvider {
+ constructor({ invitesService }) {
+ this.invitesService = invitesService;
+ }
-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}`);
-};
+ provide() {
+ const createAppInvite = async (inviterNpub) => {
+ const appInvite = await this.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/constants.js b/src/constants.js
index 8bec536..7af448b 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -1,7 +1,15 @@
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',
+ home: '/home',
+};
module.exports = {
DEFAULT_SESSION_DURATION_SECONDS,
DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS,
+ API_PATHS,
+ DEFAULT_REDIRECT_DELAY,
};
diff --git a/src/database/associations.js b/src/database/associations.js
new file mode 100644
index 0000000..45747be
--- /dev/null
+++ b/src/database/associations.js
@@ -0,0 +1,36 @@
+class AssociationsDefiner {
+ constructor({ models, DataTypes }) {
+ this.models = models;
+ this.DataTypes = DataTypes;
+ }
+
+ 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',
+ });
+ 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/config.js b/src/database/config.js
new file mode 100644
index 0000000..2800b22
--- /dev/null
+++ b/src/database/config.js
@@ -0,0 +1,21 @@
+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,
+ logging: console.log,
+ define: {
+ timestamps: false,
+ freezeTableName: true,
+ underscored: true,
+ quoteIdentifiers: false,
+ },
+ },
+};
diff --git a/src/database.js b/src/database/database.js
similarity index 74%
rename from src/database.js
rename to src/database/database.js
index b90f77f..ed4ef6c 100644
--- a/src/database.js
+++ b/src/database/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,
@@ -18,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/database/migrations/20250308000000-first-schema-tables.js b/src/database/migrations/20250308000000-first-schema-tables.js
new file mode 100644
index 0000000..3fb5cb9
--- /dev/null
+++ b/src/database/migrations/20250308000000-first-schema-tables.js
@@ -0,0 +1,379 @@
+'use strict';
+
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ 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 }
+ ),
+ 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(
+ '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_completed',
+ {
+ uuid: {
+ type: Sequelize.UUID,
+ allowNull: false,
+ unique: true,
+ primaryKey: true,
+ },
+ challenge: {
+ type: Sequelize.STRING,
+ allowNull: false,
+ unique: true,
+ },
+ signed_event: {
+ type: Sequelize.JSONB,
+ allowNull: false,
+ },
+ public_key: {
+ type: Sequelize.STRING,
+ allowNull: false,
+ },
+ created_at: {
+ type: Sequelize.DATE,
+ allowNull: false,
+ },
+ },
+ { 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(
+ '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 }
+ ),
+ 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 }
+ ),
+ 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 }
+ ),
+ 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 }
+ ),
+ 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 }
+ ),
+ 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 }
+ ),
+ 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 }
+ ),
+ ]);
+ });
+ },
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.dropTable('Users');
+ },
+};
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..2052b0a
--- /dev/null
+++ b/src/database/migrations/20250308000001-first-schema-fks.js
@@ -0,0 +1,152 @@
+'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',
+ },
+ { transaction: t }
+ ),
+ queryInterface.addConstraint(
+ 'nostr_challenge_completed',
+ {
+ fields: ['challenge'],
+ type: 'foreign key',
+ references: {
+ table: 'nostr_challenge_created',
+ field: 'challenge',
+ },
+ onDelete: 'cascade',
+ onUpdate: 'cascade',
+ },
+ { 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',
+ },
+ { transaction: t }
+ ),
+ queryInterface.addConstraint(
+ 'offer_deleted',
+ {
+ fields: ['offer_uuid'],
+ type: 'foreign key',
+ references: {
+ table: 'offer_created',
+ field: 'uuid',
+ },
+ onDelete: 'cascade',
+ onUpdate: 'cascade',
+ },
+ { transaction: t }
+ ),
+ queryInterface.addConstraint(
+ 'offer_details_set',
+ {
+ fields: ['offer_uuid'],
+ type: 'foreign key',
+ references: {
+ table: 'offer_created',
+ field: 'uuid',
+ },
+ onDelete: 'cascade',
+ onUpdate: 'cascade',
+ },
+ { 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',
+ },
+ { 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',
+ },
+ { 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',
+ },
+ { 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',
+ },
+ { 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',
+ },
+ { transaction: t }
+ ),
+ ]);
+ });
+ },
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.dropTable('Users');
+ },
+};
diff --git a/src/dependencies.js b/src/dependencies.js
new file mode 100644
index 0000000..af0495b
--- /dev/null
+++ b/src/dependencies.js
@@ -0,0 +1,52 @@
+const express = require('express');
+
+function buildDependencies() {
+ const dependencies = {};
+ const errors = require('./errors');
+ const constants = require('./constants');
+
+ const sequelize = require('./database/database');
+ const { DataTypes } = require('sequelize');
+ const ModelsProvider = require('./models');
+ const models = new ModelsProvider({ sequelize, DataTypes }).provide();
+
+ const AssociationsDefiner = require('./database/associations');
+ new AssociationsDefiner({ models, DataTypes }).define();
+
+ const ServicesProvider = require('./services');
+ const services = new ServicesProvider({
+ models,
+ constants,
+ errors,
+ sequelize,
+ }).provide();
+ dependencies.services = services;
+
+ 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,
+ middlewares,
+ invitesService: services.invitesService,
+ });
+ dependencies.webRoutes = webRoutesProvider.provide();
+
+ const ApiRoutesProvider = require('./routes/apiRoutes');
+ const apiRoutesProvider = new ApiRoutesProvider({
+ express,
+ middlewares,
+ services,
+ errors,
+ });
+ dependencies.apiRoutes = apiRoutesProvider.provide();
+ return dependencies;
+}
+
+module.exports = { buildDependencies };
diff --git a/src/front/components/BuyOrSellButtonGroup.js b/src/front/components/BuyOrSellButtonGroup.js
new file mode 100644
index 0000000..d563e54
--- /dev/null
+++ b/src/front/components/BuyOrSellButtonGroup.js
@@ -0,0 +1,58 @@
+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';
+ }
+ }
+}
+
+module.exports = BuyOrSellButtonGroup;
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/PremiumSelector.js b/src/front/components/PremiumSelector.js
new file mode 100644
index 0000000..d2defdd
--- /dev/null
+++ b/src/front/components/PremiumSelector.js
@@ -0,0 +1,68 @@
+class PremiumSelector {
+ constructor({ parentElement, id, eventSink }) {
+ this.element = null;
+ this.parentElement = parentElement;
+ this.id = id;
+ this.eventSink = eventSink;
+
+ 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;
+ this.eventSink.dispatchEvent(new Event('premium-changed'));
+ }
+
+ getPremium() {
+ return parseInt(this.premiumValue.textContent.match(/-?\d+/)[0]) / 100;
+ }
+}
+
+module.exports = PremiumSelector;
diff --git a/src/front/components/PriceDisplay.js b/src/front/components/PriceDisplay.js
new file mode 100644
index 0000000..39eb16d
--- /dev/null
+++ b/src/front/components/PriceDisplay.js
@@ -0,0 +1,64 @@
+const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces');
+
+class PriceDisplay {
+ constructor({
+ parentElement,
+ id,
+ premiumProvidingCallback,
+ priceProvidingCallback,
+ }) {
+ this.element = null;
+ this.parentElement = parentElement;
+ this.id = id;
+ this.premiumProvidingCallback = premiumProvidingCallback;
+ this.priceProvidingCallback = priceProvidingCallback;
+ }
+
+ render() {
+ const container = document.createElement('div');
+ container.id = 'premium-price-display-area';
+
+ const offerParagraph = document.createElement('p');
+ offerParagraph.id = 'offer-price-paragraph';
+ offerParagraph.textContent = 'Tu precio: ';
+
+ const offerSpan = document.createElement('span');
+ offerSpan.id = 'offer-price';
+ this.offerPriceSpan = offerSpan;
+
+ offerParagraph.appendChild(offerSpan);
+ offerParagraph.append('€/BTC');
+
+ const marketParagraph = document.createElement('p');
+ marketParagraph.id = 'market-price-paragraph';
+ marketParagraph.textContent = '(Precio mercado: ';
+
+ const marketSpan = document.createElement('span');
+ marketSpan.id = 'market-price';
+ this.marketPriceSpan = marketSpan;
+
+ marketParagraph.appendChild(marketSpan);
+ marketParagraph.append('€/BTC)');
+
+ container.appendChild(offerParagraph);
+ container.appendChild(marketParagraph);
+
+ this.updatePrices();
+
+ this.element = container;
+ this.parentElement.appendChild(this.element);
+ }
+
+ updatePrices() {
+ const marketPrice = this.priceProvidingCallback();
+ const marketPriceString = formatNumberWithSpaces(marketPrice);
+ const offerPriceString = formatNumberWithSpaces(
+ Math.round(marketPrice * (1 + this.premiumProvidingCallback()))
+ );
+
+ this.marketPriceSpan.innerText = marketPriceString;
+ this.offerPriceSpan.innerText = offerPriceString;
+ }
+}
+
+module.exports = PriceDisplay;
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/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/components/nostrButtons.js b/src/front/components/nostrButtons.js
new file mode 100644
index 0000000..31513f5
--- /dev/null
+++ b/src/front/components/nostrButtons.js
@@ -0,0 +1,63 @@
+class NostrButton {
+ constructor({ parentElement, id, onClickCallback, buttonText }) {
+ this.element = null;
+ this.parentElement = parentElement;
+ this.id = id;
+ this.onClickCallback = onClickCallback;
+ this.buttonText = buttonText;
+ }
+
+ 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 = this.buttonText;
+
+ thisButton.appendChild(figure);
+ thisButton.appendChild(paragraph);
+
+ thisButton.addEventListener('click', () => {
+ this.onClickCallback();
+ });
+
+ this.element = thisButton;
+ this.parentElement.appendChild(this.element);
+ }
+
+ disable() {
+ if (this.element) {
+ this.element.disabled = true;
+ }
+ }
+}
+
+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/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/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/invite.js b/src/front/pages/invite.js
new file mode 100644
index 0000000..24bc3b5
--- /dev/null
+++ b/src/front/pages/invite.js
@@ -0,0 +1,110 @@
+const checkNostrExtension = require('../utils/checkNostrExtension');
+const inviteService = require('../services/inviteService');
+const constants = require('../../constants');
+const { NostrSignupButton } = require('../components/nostrButtons');
+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',
+ onClickCallback: async () => {
+ const verifyResponse =
+ await inviteService.requestAndRespondSignUpChallenge({
+ onNostrErrorCallback: () => {
+ rejectedNostrWarning.display();
+ },
+ });
+
+ if (verifyResponse.ok) {
+ signUpSuccessPopup.display();
+ setTimeout(() => {
+ window.location.href = constants.API_PATHS.createProfile;
+ }, constants.DEFAULT_REDIRECT_DELAY);
+ }
+ },
+ });
+ 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();
+
+ 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,
+ successCallback: () => {
+ console.log('Nostr extension present');
+ },
+ failureCallback: () => {
+ console.log('Nostr extension not present');
+ signupButton.disable();
+ noExtensionWarning.display();
+ },
+ });
+ };
+};
+
+invitesFunction();
diff --git a/src/front/pages/login.js b/src/front/pages/login.js
new file mode 100644
index 0000000..5620a99
--- /dev/null
+++ b/src/front/pages/login.js
@@ -0,0 +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 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({
+ window,
+ successCallback: () => {
+ console.log('Nostr extension present');
+ },
+ failureCallback: () => {
+ console.log('Nostr extension not present');
+ nostrLoginButton.disable();
+ noExtensionWarning.display();
+ },
+ });
+ };
+};
+
+loginFunction();
diff --git a/src/front/pages/offers.js b/src/front/pages/offers.js
new file mode 100644
index 0000000..1913e7a
--- /dev/null
+++ b/src/front/pages/offers.js
@@ -0,0 +1,694 @@
+const formatNumberWithSpaces = require('../utils/formatNumbersWithSpaces');
+const PublishOfferButton = require('../components/PublishOfferButton');
+const BuyOrSellButtonGroup = require('../components/BuyOrSellButtonGroup');
+const PremiumSelector = require('../components/PremiumSelector');
+const PriceDisplay = require('../components/PriceDisplay');
+
+function offersPage() {
+ const createOfferEventBus = new EventTarget();
+
+ 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 buyOrSellButtonGroup = new BuyOrSellButtonGroup({
+ parentElement: document.getElementById('buy-or-sell-area'),
+ id: 'button-group-buy-or-sell',
+ });
+ buyOrSellButtonGroup.render();
+
+ const premiumSelector = new PremiumSelector({
+ parentElement: document.getElementById('premium-content-area'),
+ id: 'premium-selector-area',
+ eventSink: createOfferEventBus,
+ });
+ premiumSelector.render();
+
+ const priceDisplay = new PriceDisplay({
+ parentElement: document.getElementById('premium-content-area'),
+ id: 'premium-price-display-area',
+ premiumProvidingCallback: () => {
+ return premiumSelector.getPremium();
+ },
+ priceProvidingCallback: () => {
+ return Math.floor(Math.random() * (95000 - 70000 + 1) + 70000);
+ },
+ });
+ priceDisplay.render();
+ createOfferEventBus.addEventListener('premium-changed', () => {
+ priceDisplay.updatePrices();
+ });
+
+ // -----------
+ 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'
+ );
+ 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 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 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 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() {
+ const wants = buyOrSellButtonGroup.wants();
+
+ const premium = premiumSelector.getPremium();
+ 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();
+ });
+
+ 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);
+ });
+
+ updateBtcInput();
+
+ const myOffers = new MyOffers(ownOffersContainer);
+}
+
+offersPage();
diff --git a/src/public/javascript/invite.js b/src/front/services/inviteService.js
similarity index 61%
rename from src/public/javascript/invite.js
rename to src/front/services/inviteService.js
index 04ef5c8..d494c6c 100644
--- a/src/public/javascript/invite.js
+++ b/src/front/services/inviteService.js
@@ -1,14 +1,4 @@
-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');
- }
-};
-
-async function acceptInvite() {
+const requestAndRespondSignUpChallenge = async ({ onNostrErrorCallback }) => {
let challengeResponse;
try {
challengeResponse = await fetch('/api/signup/nostr-challenge', {
@@ -28,7 +18,7 @@ async function acceptInvite() {
try {
pubkey = await window.nostr.getPublicKey();
} catch (error) {
- document.querySelector('#rejected-nostr-nudges').style.display = 'block';
+ onNostrErrorCallback();
return;
}
const event = {
@@ -43,7 +33,7 @@ async function acceptInvite() {
try {
signedEvent = await window.nostr.signEvent(event);
} catch (error) {
- document.querySelector('#rejected-nostr-nudges').style.display = 'block';
+ onNostrErrorCallback();
return;
}
@@ -59,10 +49,9 @@ async function acceptInvite() {
return;
}
- if (verifyResponse.ok) {
- document.querySelector('#sign-up-success').style.display = 'block';
- setTimeout(() => {
- window.location.href = '/createProfile';
- }, 1000);
- }
-}
+ return verifyResponse;
+};
+
+module.exports = {
+ requestAndRespondSignUpChallenge,
+};
diff --git a/src/public/javascript/login.js b/src/front/services/loginService.js
similarity index 62%
rename from src/public/javascript/login.js
rename to src/front/services/loginService.js
index 36175b4..d1f5815 100644
--- a/src/public/javascript/login.js
+++ b/src/front/services/loginService.js
@@ -1,14 +1,12 @@
-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');
- }
-};
+const requestAndRespondLoginChallenge = async ({
+ onRejectedPubKeyCallback,
+ onRejectedSignatureCallback,
+}) => {
+ onRejectedPubKeyCallback = () => {
+ document.querySelector('#rejected-nostr-nudges').style.display = 'block';
+ };
+ onRejectedSignatureCallback = onRejectedPubKeyCallback;
-async function login() {
let challengeResponse;
try {
challengeResponse = await fetch('/api/login/nostr-challenge', {
@@ -28,7 +26,7 @@ async function login() {
try {
pubkey = await window.nostr.getPublicKey();
} catch (error) {
- document.querySelector('#rejected-nostr-nudges').style.display = 'block';
+ onRejectedPubKeyCallback();
return;
}
const event = {
@@ -43,7 +41,7 @@ async function login() {
try {
signedEvent = await window.nostr.signEvent(event);
} catch (error) {
- document.querySelector('#rejected-nostr-nudges').style.display = 'block';
+ onRejectedSignatureCallback();
return;
}
@@ -59,14 +57,9 @@ async function login() {
return;
}
- if (verifyResponse.status === 403) {
- document.querySelector('#rejected-public-key').style.display = 'block';
- }
+ return verifyResponse;
+};
- if (verifyResponse.ok) {
- document.querySelector('#sign-up-success').style.display = 'block';
- setTimeout(() => {
- window.location.href = '/home';
- }, 1000);
- }
-}
+module.exports = {
+ requestAndRespondLoginChallenge,
+};
diff --git a/src/front/utils/checkNostrExtension.js b/src/front/utils/checkNostrExtension.js
new file mode 100644
index 0000000..b16d293
--- /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;
diff --git a/src/public/javascript/utils.js b/src/front/utils/formatNumbersWithSpaces.js
similarity index 71%
rename from src/public/javascript/utils.js
rename to src/front/utils/formatNumbersWithSpaces.js
index e8964e2..1d1ee44 100644
--- a/src/public/javascript/utils.js
+++ b/src/front/utils/formatNumbersWithSpaces.js
@@ -1,3 +1,5 @@
function formatNumberWithSpaces(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
+
+module.exports = formatNumberWithSpaces;
diff --git a/src/middlewares/attachPublicKeyMiddleware.js b/src/middlewares/attachPublicKeyMiddleware.js
index c6ad835..05c7758 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/createSessionMiddleware.js b/src/middlewares/createSessionMiddleware.js
new file mode 100644
index 0000000..53e15dd
--- /dev/null
+++ b/src/middlewares/createSessionMiddleware.js
@@ -0,0 +1,39 @@
+const uuid = require('uuid');
+
+class CreateSessionMiddlewareProvider {
+ constructor({ constants, sessionService }) {
+ this.constants = constants;
+ this.sessionService = sessionService;
+ }
+
+ 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();
+ };
+ }
+
+ 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 = CreateSessionMiddlewareProvider;
diff --git a/src/middlewares/index.js b/src/middlewares/index.js
new file mode 100644
index 0000000..1a316a1
--- /dev/null
+++ b/src/middlewares/index.js
@@ -0,0 +1,55 @@
+class MiddlewaresProvider {
+ constructor({ constants, sessionService, profileService }) {
+ this.constants = constants;
+ this.sessionService = sessionService;
+ this.profileService = profileService;
+ }
+
+ provide() {
+ const AttachPublicKeyMiddlewareProvider = require('./attachPublicKeyMiddleware');
+ const attachPublicKeyMiddleware = new AttachPublicKeyMiddlewareProvider({
+ sessionService: this.sessionService,
+ }).provide();
+
+ const CreateSessionMiddlewareProvider = require('./createSessionMiddleware');
+ const createSessionMiddleware = new CreateSessionMiddlewareProvider({
+ constants: this.constants,
+ sessionService: this.sessionService,
+ }).provide();
+
+ const RejectIfNotAuthorizedMiddleware = require('./rejectIfNotAuthorizedMiddleware');
+ const rejectIfNotAuthorizedMiddleware = new RejectIfNotAuthorizedMiddleware(
+ {
+ sessionService: this.sessionService,
+ }
+ ).provide();
+
+ const RedirectHomeIfAuthorized = require('./redirectHomeIfAuthorized');
+ const redirectHomeIfAuthorized = new RedirectHomeIfAuthorized({
+ sessionService: this.sessionService,
+ }).provide();
+
+ const RedirectIfNotAuthorizedMiddleware = require('./redirectIfNotAuthorizedMiddleware');
+ const redirectIfNotAuthorizedMiddleware =
+ new RedirectIfNotAuthorizedMiddleware({
+ sessionService: this.sessionService,
+ }).provide();
+
+ const RedirectIfMissingProfileDetailsMiddleware = require('./redirectIfMissingProfileDetailsMiddleware');
+ const redirectIfMissingProfileDetailsMiddleware =
+ new RedirectIfMissingProfileDetailsMiddleware({
+ profileService: this.profileService,
+ }).provide();
+
+ return {
+ redirectIfNotAuthorizedMiddleware,
+ attachPublicKeyMiddleware,
+ redirectIfMissingProfileDetailsMiddleware,
+ redirectHomeIfAuthorized,
+ rejectIfNotAuthorizedMiddleware,
+ createSessionMiddleware,
+ };
+ }
+}
+
+module.exports = MiddlewaresProvider;
diff --git a/src/middlewares/redirectHomeIfAuthorized.js b/src/middlewares/redirectHomeIfAuthorized.js
index 9619c68..f3aedf2 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;
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;
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;
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;
diff --git a/src/middlewares/sessionMiddleware.js b/src/middlewares/sessionMiddleware.js
deleted file mode 100644
index 8d181d6..0000000
--- a/src/middlewares/sessionMiddleware.js
+++ /dev/null
@@ -1,33 +0,0 @@
-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;
- }
-
- if (sessionUuid) {
- if (!(await sessionService.isSessionValid(sessionUuid))) {
- const newSession = await setAndPersistNewSession(res);
- req.cookies.sessionUuid = newSession.uuid;
- }
- }
-
- next();
-}
-
-module.exports = createSessionMiddleware;
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..e569097 100644
--- a/src/models/NostrChallengeCompleted.js
+++ b/src/models/NostrChallengeCompleted.js
@@ -1,35 +1,43 @@
-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,
+ unique: true,
+ },
+ 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..f35ddc7 100644
--- a/src/models/NostrChallengeCreated.js
+++ b/src/models/NostrChallengeCreated.js
@@ -1,31 +1,39 @@
-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,
+ unique: true,
+ },
+ 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
new file mode 100644
index 0000000..019c61f
--- /dev/null
+++ b/src/models/OfferDeleted.js
@@ -0,0 +1,34 @@
+class OfferDeletedProvider {
+ constructor({ sequelize, DataTypes }) {
+ this.sequelize = sequelize;
+ this.DataTypes = DataTypes;
+ }
+
+ provide() {
+ const OfferDeleted = this.sequelize.define(
+ 'OfferDeleted',
+ {
+ uuid: {
+ type: this.DataTypes.UUID,
+ allowNull: false,
+ unique: true,
+ primaryKey: true,
+ },
+ offer_uuid: {
+ type: this.DataTypes.UUID,
+ 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..d2023d7 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.UUID,
+ 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
new file mode 100644
index 0000000..02b4028
--- /dev/null
+++ b/src/models/index.js
@@ -0,0 +1,97 @@
+class ModelsProvider {
+ constructor({ sequelize, DataTypes }) {
+ this.sequelize = sequelize;
+ this.DataTypes = DataTypes;
+ }
+ provide() {
+ const AppInviteCreatedProvider = require('./AppInviteCreated');
+ const AppInviteCreated = new AppInviteCreatedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const ContactDetailsSetProvider = require('./ContactDetailsSet');
+ const ContactDetailsSet = new ContactDetailsSetProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const LoginChallengeCompletedProvider = require('./LoginChallengeCompleted');
+ const LoginChallengeCompleted = new LoginChallengeCompletedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const LoginChallengeCreatedProvider = require('./LoginChallengeCreated');
+ const LoginChallengeCreated = new LoginChallengeCreatedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const NostrChallengeCompletedProvider = require('./NostrChallengeCompleted');
+ const NostrChallengeCompleted = new NostrChallengeCompletedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const NostrChallengeCreatedProvider = require('./NostrChallengeCreated');
+ const NostrChallengeCreated = new NostrChallengeCreatedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const NymSetProvider = require('./NymSet');
+ const NymSet = new NymSetProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const OfferCreatedProvider = require('./OfferCreated');
+ const OfferCreated = new OfferCreatedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const OfferDeletedProvider = require('./OfferDeleted');
+ const OfferDeleted = new OfferDeletedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const OfferDetailsSetProvider = require('./OfferDetailsSet');
+ const OfferDetailsSet = new OfferDetailsSetProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const SessionCreatedProvider = require('./SessionCreated');
+ const SessionCreated = new SessionCreatedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const SessionRelatedToPublickeyProvider = require('./SessionRelatedToPublickey');
+ const SessionRelatedToPublickey = new SessionRelatedToPublickeyProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const SignUpChallengeCompletedProvider = require('./SignUpChallengeCompleted');
+ const SignUpChallengeCompleted = new SignUpChallengeCompletedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+ const SignUpChallengeCreatedProvider = require('./SignUpChallengeCreated');
+ const SignUpChallengeCreated = new SignUpChallengeCreatedProvider({
+ sequelize: this.sequelize,
+ DataTypes: this.DataTypes,
+ }).provide();
+
+ return {
+ SignUpChallengeCreated,
+ SignUpChallengeCompleted,
+ SessionRelatedToPublickey,
+ SessionCreated,
+ OfferDeleted,
+ OfferDetailsSet,
+ OfferCreated,
+ NymSet,
+ NostrChallengeCreated,
+ NostrChallengeCompleted,
+ LoginChallengeCompleted,
+ LoginChallengeCreated,
+ ContactDetailsSet,
+ AppInviteCreated,
+ };
+ }
+}
+
+module.exports = ModelsProvider;
diff --git a/src/public/javascript/app.js b/src/public/javascript/app.js
deleted file mode 100644
index aa28857..0000000
--- a/src/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';
-});
diff --git a/src/public/javascript/createProfile.js b/src/public/javascript/createProfile.js
deleted file mode 100644
index f7af1f1..0000000
--- a/src/public/javascript/createProfile.js
+++ /dev/null
@@ -1,116 +0,0 @@
-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 }),
- });
-
- setTimeout(() => {
- window.location.href = '/home';
- }, 1000);
-}
-
-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/public/javascript/home.js b/src/public/javascript/home.js
deleted file mode 100644
index e69de29..0000000
diff --git a/src/public/javascript/offers.js b/src/public/javascript/offers.js
deleted file mode 100644
index 3a73d12..0000000
--- a/src/public/javascript/offers.js
+++ /dev/null
@@ -1,201 +0,0 @@
-const buttonStartCreateOffer = document.getElementById(
- 'button-start-create-offer'
-);
-const closeOfferControls = document.getElementById('close-offer-controls-x');
-const createOfferControls = document.getElementById('create-offer-controls');
-
-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');
-
-function toggleCreateOfferControls() {
- createOfferControls.style.display =
- createOfferControls.style.display === 'block' ? 'none' : 'block';
-
- buttonStartCreateOffer.style.display =
- buttonStartCreateOffer.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;
- }
-}
-
-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 }),
- });
-}
-
-buttonStartCreateOffer.addEventListener('click', () => {
- toggleCreateOfferControls();
-});
-
-closeOfferControls.addEventListener('click', () => {
- toggleCreateOfferControls();
-});
-
-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);
- });
-}
-
-publishOfferButton.addEventListener('click', () => {
- publishOffer();
-});
-
-updateBtcInput();
diff --git a/src/public/kitty.jpeg b/src/public/kitty.jpeg
deleted file mode 100644
index ed1ff6f..0000000
Binary files a/src/public/kitty.jpeg and /dev/null differ
diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js
index 547cdb3..102cf6a 100644
--- a/src/routes/apiRoutes.js
+++ b/src/routes/apiRoutes.js
@@ -1,237 +1,301 @@
-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 errors = require('../errors');
-const attachPublicKeyMiddleware = require('../middlewares/attachPublicKeyMiddleware');
-const rejectIfNotAuthorizedMiddleware = require('../middlewares/rejectIfNotAuthorizedMiddleware');
-
-const router = express.Router();
-
-router.get('/signup/nostr-challenge', async (req, res) => {
- const inviteUuid = req.cookies.inviteUuid;
-
- 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.',
- });
- }
-
- if (error instanceof errors.AlreadyUsedError) {
- return res.status(410).json({
- success: false,
- message: 'That invite has already been used.',
- });
- }
-
- return res.status(500).json({
- success: false,
- message: 'Unexpected error.',
- });
+class ApiRoutesProvider {
+ constructor({ express, middlewares, services, errors }) {
+ this.router = express.Router();
+ this.middlewares = middlewares;
+ this.services = services;
+ this.errors = errors;
}
- let relatedNostrChallenge;
- try {
- relatedNostrChallenge = await nostrService.getNostrChallenge(
- signUpChallenge.nostr_challenge_uuid
+ provide() {
+ this.router.get('/signup/nostr-challenge', async (req, res) => {
+ const inviteUuid = req.cookies.inviteUuid;
+
+ let signUpChallenge;
+ try {
+ signUpChallenge =
+ await this.services.invitesService.createSignUpChallenge(inviteUuid);
+ } catch (error) {
+ if (error instanceof this.errors.NotFoundError) {
+ return res.status(404).json({
+ success: false,
+ message: 'Could not find invite with that id.',
+ });
+ }
+
+ if (error instanceof this.errors.AlreadyUsedError) {
+ return res.status(410).json({
+ success: false,
+ message: 'That invite has already been used.',
+ });
+ }
+
+ return res.status(500).json({
+ success: false,
+ message: 'Unexpected error.',
+ });
+ }
+
+ let relatedNostrChallenge;
+ try {
+ relatedNostrChallenge =
+ await this.services.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 });
+ });
+
+ this.router.post('/signup/nostr-verify', async (req, res) => {
+ const signedEvent = req.body;
+ const sessionUuid = req.cookies.sessionUuid;
+
+ let completedSignUpChallenge;
+ try {
+ completedSignUpChallenge =
+ await this.services.invitesService.verifySignUpChallenge(signedEvent);
+ } catch (error) {
+ 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 this.errors.AlreadyUsedError) {
+ return res.status(410).json({
+ success: false,
+ message: 'The challenge has been used, request a new one.',
+ });
+ }
+ if (error instanceof this.errors.InvalidSignatureError) {
+ return res.status(400).json({
+ success: false,
+ message: 'The challenge signature is not valid.',
+ });
+ }
+ }
+
+ await this.services.sessionService.relateSessionToPublicKey(
+ sessionUuid,
+ completedSignUpChallenge.public_key
+ );
+
+ return res.status(200).json({ success: true });
+ });
+
+ this.router.get('/login/nostr-challenge', async (req, res) => {
+ let loginChallenge;
+ try {
+ loginChallenge =
+ await this.services.loginService.createLoginChallenge();
+ } catch (error) {
+ return res.status(500).json({
+ success: false,
+ message: 'Unexpected error.',
+ });
+ }
+
+ let relatedNostrChallenge;
+ try {
+ relatedNostrChallenge =
+ await this.services.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 });
+ });
+
+ this.router.post('/login/nostr-verify', async (req, res) => {
+ const signedEvent = req.body;
+ const sessionUuid = req.cookies.sessionUuid;
+
+ let completedLoginChallenge;
+ try {
+ completedLoginChallenge =
+ await this.services.loginService.verifyLoginChallenge(signedEvent);
+ } catch (error) {
+ 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 this.errors.AlreadyUsedError) {
+ return res.status(410).json({
+ success: false,
+ message: 'The challenge has been used, request a new one.',
+ });
+ }
+ if (error instanceof this.errors.InvalidSignatureError) {
+ return res.status(400).json({
+ success: false,
+ message: 'The challenge signature is not valid.',
+ });
+ }
+ if (error instanceof this.errors.ForbiddenError) {
+ return res.status(403).json({
+ success: false,
+ message: 'This public key is not authorized.',
+ });
+ }
+
+ return res.status(500).json({
+ success: false,
+ message: 'Unexpected error.',
+ });
+ }
+
+ await this.services.sessionService.relateSessionToPublicKey(
+ sessionUuid,
+ completedLoginChallenge.public_key
+ );
+
+ return res.status(200).json({ success: true });
+ });
+
+ this.router.post(
+ '/set-contact-details',
+ this.middlewares.rejectIfNotAuthorizedMiddleware,
+ this.middlewares.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 this.services.profileService.setContactDetails(
+ publicKey,
+ encryptedContactDetails
+ );
+
+ return res.status(200).json({
+ success: true,
+ message: 'Contact details set successfully.',
+ });
+ }
);
- } catch (error) {
- return res.status(500).json({
- success: false,
- message: `Unexpected error: ${error}`,
- });
- }
- return res.status(200).json({ challenge: relatedNostrChallenge.challenge });
-});
+ this.router.post(
+ '/set-nym',
+ this.middlewares.rejectIfNotAuthorizedMiddleware,
+ this.middlewares.attachPublicKeyMiddleware,
+ async (req, res) => {
+ const nym = req.body.nym;
+ const publicKey = req.cookies.publicKey;
-router.post('/signup/nostr-verify', async (req, res) => {
- const signedEvent = req.body;
- const sessionUuid = req.cookies.sessionUuid;
+ if (!nym) {
+ return res.status(400).json({
+ success: false,
+ message: 'Missing nym',
+ });
+ }
- 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 this.services.profileService.setNym(publicKey, nym);
- 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
+ return res.status(200).json({
+ success: true,
+ message: 'Nym set successfully.',
+ });
+ }
);
- } catch (error) {
- return res.status(500).json({
- success: false,
- message: `Unexpected error: ${error}`,
- });
+
+ this.router.post(
+ '/offer',
+ this.middlewares.rejectIfNotAuthorizedMiddleware,
+ this.middlewares.attachPublicKeyMiddleware,
+ async (req, res) => {
+ const publicKey = req.cookies.publicKey;
+ const offerDetails = req.body.offerDetails;
+
+ await this.services.offerService.createOffer(publicKey, offerDetails);
+
+ return res.status(200).json({
+ success: true,
+ message: 'Offer created successfully',
+ });
+ }
+ );
+
+ this.router.delete(
+ '/offer/:offerUuid',
+ this.middlewares.rejectIfNotAuthorizedMiddleware,
+ this.middlewares.attachPublicKeyMiddleware,
+ async (req, res) => {
+ const offerUuid = req.params.offerUuid;
+
+ try {
+ await this.services.offerService.deleteOffer(offerUuid);
+ } catch (error) {
+ if (error instanceof this.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',
+ });
+ }
+ );
+
+ this.router.get(
+ '/publickey-offers',
+ this.middlewares.rejectIfNotAuthorizedMiddleware,
+ this.middlewares.attachPublicKeyMiddleware,
+ async (req, res) => {
+ console.log('elo');
+ const publicKey = req.cookies.publicKey;
+
+ const offers =
+ await this.services.offerService.getActiveOffersByPublicKey(
+ 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 this.router;
}
-
- 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',
- });
- }
-);
-
-module.exports = router;
+}
+module.exports = ApiRoutesProvider;
diff --git a/src/routes/webRoutes.js b/src/routes/webRoutes.js
index fd6585e..b215aa5 100644
--- a/src/routes/webRoutes.js
+++ b/src/routes/webRoutes.js
@@ -1,74 +1,88 @@
-const express = require('express');
-const router = express.Router();
-
-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');
-
-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' });
+class WebRoutesProvider {
+ constructor({ express, middlewares, invitesService }) {
+ this.router = express.Router();
+ this.middlewares = middlewares;
+ this.invitesService = invitesService;
}
- return res.render('invite', { invite });
-});
+ provide() {
+ this.router.get(
+ '/',
+ this.middlewares.redirectHomeIfAuthorized,
+ this.middlewares.redirectIfNotAuthorizedMiddleware,
+ (req, res) => {
+ res.redirect('/login');
+ }
+ );
-router.get(
- '/createProfile',
- redirectIfNotAuthorizedMiddleware,
- async (req, res) => {
- return res.status(200).render('createProfile');
+ 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;
+
+ res.cookie('inviteUuid', inviteUuid, {
+ httpOnly: true,
+ maxAge: 86400000,
+ });
+
+ let invite;
+ try {
+ invite = await this.invitesService.getAppInvite(inviteUuid);
+ if (!invite) {
+ return res
+ .status(404)
+ .render('error', { message: 'Invite not found.' });
+ }
+
+ if (await this.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 });
+ });
+
+ this.router.get(
+ '/createProfile',
+ this.middlewares.redirectIfNotAuthorizedMiddleware,
+ async (req, res) => {
+ return res.status(200).render('createProfile');
+ }
+ );
+
+ this.router.get(
+ '/home',
+ this.middlewares.redirectIfNotAuthorizedMiddleware,
+ this.middlewares.attachPublicKeyMiddleware,
+ this.middlewares.redirectIfMissingProfileDetailsMiddleware,
+ (req, res) => {
+ res.render('home', {});
+ }
+ );
+
+ this.router.get(
+ '/offers',
+ this.middlewares.redirectIfNotAuthorizedMiddleware,
+ this.middlewares.attachPublicKeyMiddleware,
+ this.middlewares.redirectIfMissingProfileDetailsMiddleware,
+ (req, res) => {
+ res.render('offers', {});
+ }
+ );
+
+ return this.router;
}
-);
+}
-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;
diff --git a/src/services/index.js b/src/services/index.js
new file mode 100644
index 0000000..1ec7ea2
--- /dev/null
+++ b/src/services/index.js
@@ -0,0 +1,61 @@
+class ServicesProvider {
+ constructor({ models, constants, errors, sequelize }) {
+ this.models = models;
+ this.constants = constants;
+ this.errors = errors;
+ this.sequelize = sequelize;
+ }
+
+ provide() {
+ const NostrServiceProvider = require('../services/nostrService');
+ const nostrService = new NostrServiceProvider({
+ models: this.models,
+ constants: this.constants,
+ errors: this.errors,
+ }).provide();
+
+ const InvitesServiceProvider = require('../services/invitesService');
+ const invitesService = new InvitesServiceProvider({
+ models: this.models,
+ errors: this.errors,
+ nostrService: nostrService,
+ }).provide();
+
+ const LoginServiceProvider = require('../services/loginService');
+ const loginService = new LoginServiceProvider({
+ models: this.models,
+ errors: this.errors,
+ nostrService,
+ invitesService,
+ }).provide();
+
+ const SessionServiceProvider = require('../services/sessionService');
+ const sessionService = new SessionServiceProvider({
+ models: this.models,
+ constants: this.constants,
+ invitesService,
+ }).provide();
+
+ const ProfileServiceProvider = require('../services/profileService');
+ const profileService = new ProfileServiceProvider({
+ models: this.models,
+ }).provide();
+ const OfferServiceProvider = require('../services/offerService');
+ const offerService = new OfferServiceProvider({
+ models: this.models,
+ errors: this.errors,
+ sequelize: this.sequelize,
+ }).provide();
+
+ return {
+ invitesService,
+ nostrService,
+ loginService,
+ sessionService,
+ profileService,
+ offerService,
+ };
+ }
+}
+
+module.exports = ServicesProvider;
diff --git a/src/services/invitesService.js b/src/services/invitesService.js
index 432633c..df357e3 100644
--- a/src/services/invitesService.js
+++ b/src/services/invitesService.js
@@ -1,119 +1,134 @@
const uuid = require('uuid');
-const nostrService = require('./nostrService');
-const AppInviteCreated = require('../models/AppInviteCreated');
-const SignUpChallengeCreated = require('../models/SignUpChallengeCreated');
-const SignUpChallengeCompleted = require('../models/SignUpChallengeCompleted');
-
-const errors = require('../errors');
-
-async function appInviteExists(inviteUuid) {
- const invite = await AppInviteCreated.findOne({
- where: { uuid: inviteUuid },
- });
- if (invite) {
- return true;
- }
- return false;
-}
-
-async function getAppInvite(inviteUuid) {
- const invite = await AppInviteCreated.findOne({
- where: { uuid: inviteUuid },
- });
- return invite;
-}
-
-async function isAppInviteSpent(appInviteUuid) {
- const signUpChallengeCompleted = await SignUpChallengeCompleted.findOne({
- where: {
- app_invite_uuid: appInviteUuid,
- },
- });
-
- if (signUpChallengeCompleted) {
- return true;
- }
- return false;
-}
-
-async function createAppInvite(inviterPubKey) {
- return await 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({ models, errors, nostrService }) {
+ this.models = models;
+ this.errors = errors;
+ this.nostrService = nostrService;
}
- if (await isAppInviteSpent(appInviteUuid)) {
- throw new errors.AlreadyUsedError('Invite has already been used.');
+ provide() {
+ const appInviteExists = async (inviteUuid) => {
+ const invite = await this.models.AppInviteCreated.findOne({
+ where: { uuid: inviteUuid },
+ });
+ if (invite) {
+ return true;
+ }
+ return false;
+ };
+
+ const getAppInvite = async (inviteUuid) => {
+ const invite = await this.models.AppInviteCreated.findOne({
+ where: { uuid: inviteUuid },
+ });
+ return invite;
+ };
+
+ const isAppInviteSpent = async (appInviteUuid) => {
+ const signUpChallengeCompleted =
+ await this.models.SignUpChallengeCompleted.findOne({
+ where: {
+ app_invite_uuid: appInviteUuid,
+ },
+ });
+
+ if (signUpChallengeCompleted) {
+ return true;
+ }
+ return false;
+ };
+
+ 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 this.errors.NotFoundError("Invite doesn't exist.");
+ }
+
+ if (await isAppInviteSpent(appInviteUuid)) {
+ throw new this.errors.AlreadyUsedError('Invite has already been used.');
+ }
+
+ const nostrChallenge = await this.nostrService.createNostrChallenge();
+
+ return await this.models.SignUpChallengeCreated.create({
+ uuid: uuid.v7(),
+ nostr_challenge_uuid: nostrChallenge.uuid,
+ app_invite_uuid: appInviteUuid,
+ created_at: new Date().toISOString(),
+ });
+ };
+
+ const verifySignUpChallenge = async (signedEvent) => {
+ const challengeTag = signedEvent.tags.find(
+ (tag) => tag[0] === 'challenge'
+ );
+ const challenge = challengeTag[1];
+
+ const nostrChallenge = await this.nostrService.getNostrChallenge(
+ null,
+ challenge
+ );
+
+ const signUpChallenge = await this.models.SignUpChallengeCreated.findOne({
+ where: {
+ nostr_challenge_uuid: nostrChallenge.uuid,
+ },
+ });
+
+ if (await this.nostrService.hasNostrChallengeBeenCompleted(challenge)) {
+ throw new this.errors.AlreadyUsedError(
+ 'This challenge has already been used.'
+ );
+ }
+
+ const completedNostrChallenge =
+ await this.nostrService.verifyNostrChallenge(signedEvent);
+
+ const completedSignUpChallenge =
+ await this.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;
+ };
+
+ const isPublicKeySignedUp = async (publicKey) => {
+ const signUpChallengeCompleted =
+ await this.models.SignUpChallengeCompleted.findOne({
+ where: {
+ public_key: publicKey,
+ },
+ });
+
+ if (signUpChallengeCompleted) {
+ return true;
+ }
+
+ return false;
+ };
+
+ return {
+ appInviteExists,
+ getAppInvite,
+ isAppInviteSpent,
+ createAppInvite,
+ createSignUpChallenge,
+ verifySignUpChallenge,
+ isPublicKeySignedUp,
+ };
}
-
- const nostrChallenge = await nostrService.createNostrChallenge();
-
- return await 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 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 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,
- },
- });
-
- if (signUpChallengeCompleted) {
- return true;
- }
-
- return false;
-}
-
-module.exports = {
- appInviteExists,
- getAppInvite,
- isAppInviteSpent,
- createAppInvite,
- createSignUpChallenge,
- verifySignUpChallenge,
- isPublicKeySignedUp,
-};
+module.exports = InvitesServiceProvider;
diff --git a/src/services/loginService.js b/src/services/loginService.js
index 9d5f973..73a0b58 100644
--- a/src/services/loginService.js
+++ b/src/services/loginService.js
@@ -1,58 +1,64 @@
const uuid = require('uuid');
-const nostrService = require('./nostrService');
-const invitesService = require('./invitesService');
-const LoginChallengeCreated = require('../models/LoginChallengeCreated');
-const LoginChallengeCompleted = require('../models/LoginChallengeCompleted');
-
-const errors = require('../errors');
-
-async function createLoginChallenge() {
- const nostrChallenge = await nostrService.createNostrChallenge();
-
- return await 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({ models, errors, nostrService, invitesService }) {
+ this.models = models;
+ this.errors = errors;
+ 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 this.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 this.errors.AlreadyUsedError(
+ 'This challenge has already been used.'
+ );
+ }
+
+ const completedNostrChallenge =
+ await this.nostrService.verifyNostrChallenge(signedEvent);
+
+ if (
+ !(await this.invitesService.isPublicKeySignedUp(
+ completedNostrChallenge.public_key
+ ))
+ ) {
+ throw new this.errors.ForbiddenError(
+ `Public key ${completedNostrChallenge.public_key} is not authorized.`
+ );
+ }
+
+ const completedLoginChallenge =
+ await this.models.LoginChallengeCompleted.create({
+ uuid: uuid.v7(),
+ nostr_challenge_completed_uuid: completedNostrChallenge.uuid,
+ public_key: completedNostrChallenge.public_key,
+ created_at: new Date().toISOString(),
+ });
+
+ return completedLoginChallenge;
+ };
+
+ return {
+ createLoginChallenge,
+ verifyLoginChallenge,
+ };
}
-
- const completedLoginChallenge = await 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;
diff --git a/src/services/nostrService.js b/src/services/nostrService.js
index 542ba1c..7d87ea6 100644
--- a/src/services/nostrService.js
+++ b/src/services/nostrService.js
@@ -1,113 +1,126 @@
const uuid = require('uuid');
const crypto = require('crypto');
-const { Op, TimeoutError } = require('sequelize');
+const { Op } = require('sequelize');
const { verifyEvent } = require('nostr-tools');
-const NostrChallengeCreated = require('../models/NostrChallengeCreated');
-const NostrChallengeCompleted = require('../models/NostrChallengeCompleted');
+class NostrServiceProvider {
+ constructor({ models, constants, errors }) {
+ this.models = models;
+ this.constants = constants;
+ this.errors = errors;
+ }
-const constants = require('../constants');
-const errors = require('../errors');
+ provide() {
+ const createNostrChallenge = async () => {
+ const currentTimestamp = new Date();
+ const expiryTimestamp = new Date(currentTimestamp.getTime());
+ expiryTimestamp.setSeconds(
+ expiryTimestamp.getSeconds() +
+ this.constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS
+ );
-async function createNostrChallenge() {
- const currentTimestamp = new Date();
- const expiryTimestamp = new Date(currentTimestamp.getTime());
- expiryTimestamp.setSeconds(
- expiryTimestamp.getSeconds() +
- constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS
- );
+ const nostrChallenge = await this.models.NostrChallengeCreated.create({
+ uuid: uuid.v7(),
+ challenge: crypto.randomBytes(32).toString('hex'),
+ expires_at: expiryTimestamp.toISOString(),
+ created_at: currentTimestamp.toISOString(),
+ });
- const nostrChallenge = await NostrChallengeCreated.create({
- uuid: uuid.v7(),
- challenge: crypto.randomBytes(32).toString('hex'),
- expires_at: expiryTimestamp.toISOString(),
- created_at: currentTimestamp.toISOString(),
- });
+ return nostrChallenge;
+ };
- return nostrChallenge;
+ const getNostrChallenge = async (
+ nostrChallengeUuid = null,
+ challenge = null
+ ) => {
+ if (nostrChallengeUuid) {
+ return await this.models.NostrChallengeCreated.findOne({
+ where: {
+ uuid: nostrChallengeUuid,
+ },
+ });
+ }
+
+ if (challenge) {
+ return await this.models.NostrChallengeCreated.findOne({
+ where: {
+ challenge,
+ },
+ });
+ }
+
+ throw Error('You need to pass a uuid or a challenge.');
+ };
+
+ const verifyNostrChallenge = async (signedEvent) => {
+ const challengeTag = signedEvent.tags.find(
+ (tag) => tag[0] === 'challenge'
+ );
+ const challenge = challengeTag[1];
+
+ if (!(await isNostrChallengeFresh(challenge))) {
+ throw new this.errors.ExpiredError(
+ 'Challenge expired, request new one.'
+ );
+ }
+
+ if (await hasNostrChallengeBeenCompleted(challenge)) {
+ throw new this.errors.AlreadyUsedError(
+ 'Challenge already used, request new one.'
+ );
+ }
+
+ const isSignatureValid = verifyEvent(signedEvent);
+ if (!isSignatureValid) {
+ throw new this.errors.InvalidSignatureError('Signature is not valid.');
+ }
+
+ return await this.models.NostrChallengeCompleted.create({
+ uuid: uuid.v7(),
+ challenge: challenge,
+ signed_event: signedEvent,
+ public_key: signedEvent.pubkey,
+ created_at: new Date().toISOString(),
+ });
+ };
+
+ const isNostrChallengeFresh = async (challengeString) => {
+ const nostrChallenge = await this.models.NostrChallengeCreated.findOne({
+ where: {
+ challenge: challengeString,
+ expires_at: {
+ [Op.gt]: new Date(),
+ },
+ },
+ });
+
+ if (nostrChallenge) {
+ return true;
+ }
+ return false;
+ };
+
+ const hasNostrChallengeBeenCompleted = async (challengeString) => {
+ const completedNostrChallenge =
+ await this.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 NostrChallengeCreated.findOne({
- where: {
- uuid: nostrChallengeUuid,
- },
- });
- }
-
- if (challenge) {
- return await 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 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 NostrChallengeCreated.findOne({
- where: {
- challenge: challengeString,
- expires_at: {
- [Op.gt]: new Date(),
- },
- },
- });
-
- if (nostrChallenge) {
- return true;
- }
- return false;
-}
-
-async function hasNostrChallengeBeenCompleted(challengeString) {
- const completedNostrChallenge = await NostrChallengeCompleted.findOne({
- where: {
- challenge: challengeString,
- },
- });
-
- if (completedNostrChallenge) {
- return true;
- }
- return false;
-}
-
-module.exports = {
- createNostrChallenge,
- getNostrChallenge,
- verifyNostrChallenge,
- isNostrChallengeFresh,
- hasNostrChallengeBeenCompleted,
-};
+module.exports = NostrServiceProvider;
diff --git a/src/services/offerService.js b/src/services/offerService.js
index cca3b39..bd65fd8 100644
--- a/src/services/offerService.js
+++ b/src/services/offerService.js
@@ -1,30 +1,126 @@
const uuid = require('uuid');
-const OfferCreated = require('../models/OfferCreated');
-const OfferDetailsSet = require('../models/OfferDetailsSet');
+class OfferServiceProvider {
+ constructor({ models, errors, sequelize }) {
+ this.models = models;
+ this.errors = errors;
+ this.sequelize = sequelize;
+ }
+ provide() {
+ const createOffer = async (publicKey, offerDetails) => {
+ const createOfferTransaction = await this.sequelize.transaction();
+ try {
+ const offerCreated = await this.models.OfferCreated.create(
+ {
+ uuid: uuid.v7(),
+ public_key: publicKey,
+ created_at: new Date().toISOString(),
+ },
+ { transaction: createOfferTransaction }
+ );
-async function createOffer(publicKey, offerDetails) {
- const offerCreated = await OfferCreated.create({
- uuid: uuid.v7(),
- public_key: publicKey,
- created_at: new Date().toISOString(),
- });
+ await this.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(),
+ },
+ { transaction: createOfferTransaction }
+ );
+ await createOfferTransaction.commit();
+ } catch (error) {
+ await createOfferTransaction.rollback();
+ }
+ };
- 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(),
- });
+ const deleteOffer = async (offerUuid) => {
+ const offerExists = Boolean(
+ await this.models.OfferCreated.findOne({ where: { uuid: offerUuid } })
+ );
+ const offerHasBeenDeleted = Boolean(
+ await this.models.OfferDeleted.findOne({
+ where: { offer_uuid: offerUuid },
+ })
+ );
+
+ if (!offerExists || offerHasBeenDeleted) {
+ throw new this.errors.NotFoundError(`Could not find the offer.`);
+ }
+
+ return this.models.OfferDeleted.create({
+ uuid: uuid.v7(),
+ offer_uuid: offerUuid,
+ created_at: new Date().toISOString(),
+ });
+ };
+
+ const getActiveOffersByPublicKey = async (publicKey) => {
+ const activeOffers = await this.models.OfferCreated.findAll({
+ where: {
+ public_key: publicKey,
+ '$OfferDeleted.uuid$': null,
+ },
+ include: { model: this.models.OfferDeleted, required: false },
+ });
+
+ console.log(activeOffers);
+
+ if (!activeOffers) {
+ return [];
+ }
+
+ const offersToReturn = [];
+ if (activeOffers) {
+ for (const someOffer of activeOffers) {
+ const offerDetails = await this.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,
+ getActiveOffersByPublicKey,
+ deleteOffer,
+ };
+ }
}
-module.exports = { createOffer };
+
+module.exports = OfferServiceProvider;
diff --git a/src/services/profileService.js b/src/services/profileService.js
index 5db0c24..aca4247 100644
--- a/src/services/profileService.js
+++ b/src/services/profileService.js
@@ -1,38 +1,44 @@
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(),
- });
+class ProfileServiceProvider {
+ constructor({ models }) {
+ this.models = models;
+ }
+ provide() {
+ 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(),
+ });
+ };
+
+ const setNym = async (publicKey, nym) => {
+ return await this.models.NymSet.create({
+ uuid: uuid.v7(),
+ public_key: publicKey,
+ nym: nym,
+ created_at: new Date().toISOString(),
+ });
+ };
+
+ const areProfileDetailsComplete = async (publicKey) => {
+ console.log(this.models);
+ const isNymSet = await this.models.NymSet.findOne({
+ where: { public_key: publicKey },
+ });
+ const areContactDetailsSet = await this.models.ContactDetailsSet.findOne({
+ where: {
+ public_key: publicKey,
+ },
+ });
+
+ return isNymSet && areContactDetailsSet;
+ };
+
+ return { setContactDetails, setNym, areProfileDetailsComplete };
+ }
}
-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,
- },
- });
-
- return isNymSet && areContactDetailsSet;
-}
-
-module.exports = {
- setContactDetails,
- setNym,
- areProfileDetailsComplete,
-};
+module.exports = ProfileServiceProvider;
diff --git a/src/services/sessionService.js b/src/services/sessionService.js
index 452f390..e731846 100644
--- a/src/services/sessionService.js
+++ b/src/services/sessionService.js
@@ -1,92 +1,101 @@
const uuid = require('uuid');
-const SessionCreated = require('../models/SessionCreated');
-const SessionRelatedToPublickey = require('../models/SessionRelatedToPublickey');
+class SessionServiceProvider {
+ constructor({ models, constants, invitesService }) {
+ this.models = models;
+ this.constants = constants;
+ this.invitesService = invitesService;
+ }
-const invitesService = require('./invitesService');
-const constants = require('../constants');
+ provide() {
+ const createSession = async (sessionUuid) => {
+ const currentTimestamp = new Date();
+ const expiryTimestamp = new Date(currentTimestamp.getTime());
+ expiryTimestamp.setSeconds(
+ expiryTimestamp.getSeconds() +
+ this.constants.DEFAULT_SESSION_DURATION_SECONDS
+ );
-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 this.models.SessionCreated.create({
+ uuid: sessionUuid,
+ created_at: currentTimestamp.toISOString(),
+ expires_at: expiryTimestamp.toISOString(),
+ });
+ };
- return await SessionCreated.create({
- uuid: sessionUuid,
- created_at: currentTimestamp.toISOString(),
- expires_at: expiryTimestamp.toISOString(),
- });
+ const isSessionValid = async (sessionUuid) => {
+ const currentSession = await this.models.SessionCreated.findOne({
+ where: {
+ uuid: sessionUuid,
+ },
+ });
+
+ if (!currentSession) {
+ return false;
+ }
+
+ if (currentSession.expires_at <= new Date()) {
+ return false;
+ }
+
+ 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 this.models.SessionRelatedToPublickey.create({
+ uuid: uuid.v7(),
+ session_uuid: sessionUuid,
+ public_key: publicKey,
+ created_at: new Date().toISOString(),
+ });
+ };
+
+ const isSessionAuthorized = async (sessionUuid) => {
+ const isSessionRelatedToPublicKey =
+ await this.models.SessionRelatedToPublickey.findOne({
+ where: {
+ session_uuid: sessionUuid,
+ },
+ });
+
+ if (isSessionRelatedToPublicKey) {
+ return true;
+ }
+
+ return false;
+ };
+
+ const getPublicKeyRelatedToSession = async (sessionUuid) => {
+ const sessionRelatedToPublickey =
+ await this.models.SessionRelatedToPublickey.findOne({
+ where: {
+ session_uuid: sessionUuid,
+ },
+ });
+
+ if (sessionRelatedToPublickey) {
+ return sessionRelatedToPublickey.public_key;
+ }
+
+ return null;
+ };
+
+ return {
+ createSession,
+ isSessionValid,
+ relateSessionToPublicKey,
+ isSessionAuthorized,
+ getPublicKeyRelatedToSession,
+ };
+ }
}
-async function isSessionValid(sessionUuid) {
- const currentSession = await SessionCreated.findOne({
- where: {
- uuid: sessionUuid,
- },
- });
-
- if (!currentSession) {
- return false;
- }
-
- if (currentSession.expires_at <= new Date()) {
- return false;
- }
-
- return true;
-}
-
-async function relateSessionToPublicKey(sessionUuid, publicKey) {
- if (!(await isSessionValid(sessionUuid))) {
- throw Error('Session is not valid anymore.');
- }
-
- if (!(await invitesService.isPublicKeySignedUp(publicKey))) {
- throw Error('Public key is not signed up.');
- }
-
- return SessionRelatedToPublickey.create({
- uuid: uuid.v7(),
- session_uuid: sessionUuid,
- public_key: publicKey,
- created_at: new Date().toISOString(),
- });
-}
-
-async function isSessionAuthorized(sessionUuid) {
- const isSessionRelatedToPublicKey = await SessionRelatedToPublickey.findOne({
- where: {
- session_uuid: sessionUuid,
- },
- });
-
- if (isSessionRelatedToPublicKey) {
- return true;
- }
-
- return false;
-}
-
-async function getPublicKeyRelatedToSession(sessionUuid) {
- const sessionRelatedToPublickey = await SessionRelatedToPublickey.findOne({
- where: {
- session_uuid: sessionUuid,
- },
- });
-
- if (sessionRelatedToPublickey) {
- return sessionRelatedToPublickey.public_key;
- }
-
- return null;
-}
-
-module.exports = {
- createSession,
- isSessionValid,
- relateSessionToPublicKey,
- isSessionAuthorized,
- getPublicKeyRelatedToSession,
-};
+module.exports = SessionServiceProvider;
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
- Home
- Ofertas
- Red
- Mi perfil
-
-
- Crear oferta
-
-
-
Crear oferta
-
-
-
diff --git a/src/views/createProfile.ejs b/src/views/createProfile.ejs
index 87b57ef..d4be5ec 100644
--- a/src/views/createProfile.ejs
+++ b/src/views/createProfile.ejs
@@ -4,131 +4,131 @@
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
-
Pseudónimo (Nym):
-
-
- Debe tener al menos 3 caracteres.
-
-
-
-
-
-
Datos de contacto
-
+
+
+
+ ¡Bien! Tu perfil está completo. Te estamos llevando a la aplicación...
+
-
+