Compare commits
No commits in common. "master" and "view-my-offers" have entirely different histories.
master
...
view-my-of
|
|
@ -1 +0,0 @@
|
|||
public/javascript/*
|
||||
6
.gitignore
vendored
|
|
@ -136,10 +136,10 @@ dist
|
|||
.pnp.*
|
||||
|
||||
|
||||
test-results/*
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
# webpack bundles
|
||||
/public/javascript/*
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
config: path.resolve('src', 'database', 'config.js'),
|
||||
'migrations-path': path.resolve('src', 'database', 'migrations'),
|
||||
};
|
||||
10
AGENTS.md
|
|
@ -1,10 +0,0 @@
|
|||
# secajs
|
||||
|
||||
## Architecture
|
||||
|
||||
This repository contains a webapp. It covers the full stack which consists of:
|
||||
- The client side code, in `src/front/`.
|
||||
- The backend service in `src/`, except for `src/front/`.
|
||||
- A Postgres database. Database connections and migrations are in `src/database/`.
|
||||
- Besides, there is an admin CLI, with entrypoint in `src/cli.js` and commands in `src/commands/`.
|
||||
|
||||
22
Dockerfile
|
|
@ -1,22 +1,10 @@
|
|||
FROM debian:12
|
||||
FROM debian:latest
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get install -y \
|
||||
curl gnupg2 ca-certificates lsb-release apt-transport-https
|
||||
|
||||
RUN apt-get install -y \
|
||||
postgresql
|
||||
|
||||
RUN apt-get install -y \
|
||||
caddy
|
||||
|
||||
RUN apt-get install -y \
|
||||
nodejs npm
|
||||
|
||||
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl gnupg2 ca-certificates lsb-release apt-transport-https \
|
||||
postgresql caddy nodejs npm && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN echo "listen_addresses='*'" >> /etc/postgresql/15/main/postgresql.conf && \
|
||||
echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/15/main/pg_hba.conf && \
|
||||
|
|
|
|||
11
README.md
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
laseca is a social bitcoin to cash exchange, implemented as a webapp.
|
||||
|
||||
## Upcoming stories
|
||||
|
||||
* [ ] Create offer
|
||||
* [ ] Display existing offers
|
||||
* [ ] Archive offer
|
||||
|
||||
## How to set up dev environment
|
||||
|
||||
* Pre-requisites
|
||||
|
|
@ -10,16 +16,11 @@ laseca is a social bitcoin to cash exchange, implemented as a webapp.
|
|||
* Installing
|
||||
+ Run `npm install`
|
||||
+ You can now start the app in a container by running `npm run start:container` (and shut it down with `npm run stop:container`).
|
||||
* Building
|
||||
+ The front-end code gets built with webpack. You can build it anytime with `npm run build`.
|
||||
+ For development, it's useful to build continuously. You can run `npm run watch` and webpack will build every time you edit a monitored file.
|
||||
* Running
|
||||
+ Copy the `.env.dist` file into `.env` and set any values you like.
|
||||
+ The app will run in a single container, with a Postgres database, a caddy webserver and the nodejs app.
|
||||
+ Note that the container doesn't come with a volume for Postgres: default behaviour is to start from scratch every time you create the container, delete everything every time you delete the container.
|
||||
+ You probably want to run migrations to get the database into proper state. You can do so with `npx sequelize-cli db:migrate`.
|
||||
+ The docker image launches the nodejs app with nodemon, so changes to the code will be available immediately.
|
||||
+ Furthermore, since the git repository gets mounted live into the docker container, the live changes made by webpack watch mode will also be available as you work on front end files.
|
||||
+ The Postgres database is reachable from the host, so you can use your favourite SQL client to access it.
|
||||
+ You can format with `npm run format` and lint with `npm run lint`.
|
||||
|
||||
|
|
|
|||
1948
package-lock.json
generated
12
package.json
|
|
@ -20,13 +20,10 @@
|
|||
"start": "node src/app.js",
|
||||
"start:container": "docker compose up -d --build",
|
||||
"stop:container": "docker compose down",
|
||||
"migrate": "npx sequelize-cli db:migrate",
|
||||
"build": "webpack",
|
||||
"watch": "webpack --watch",
|
||||
"cli": "node src/cli.js",
|
||||
"test": "playwright test",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\"",
|
||||
"test": "playwright test"
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,html,ejs}\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
@ -39,9 +36,6 @@
|
|||
"globals": "^15.15.0",
|
||||
"playwright": "^1.50.1",
|
||||
"prettier": "^3.5.1",
|
||||
"prettier-plugin-ejs": "^1.0.3",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
"webpack": "^5.98.0",
|
||||
"webpack-cli": "^6.0.1"
|
||||
"prettier-plugin-ejs": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
const { defineConfig } = require('@playwright/test');
|
||||
|
||||
module.exports = defineConfig({
|
||||
testDir: './tests',
|
||||
use: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
},
|
||||
webServer: {
|
||||
command: 'npm start',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
@media (max-width: 768px) {
|
||||
#login-button {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
#login-card-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
#login-button {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#login-card-content {
|
||||
width: 40%;
|
||||
min-width: min-content;
|
||||
}
|
||||
}
|
||||
|
||||
#login-card-content {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#login-card-content > * {
|
||||
margin: 1vh auto;
|
||||
text-align: center;
|
||||
}
|
||||
26
src/app.js
|
|
@ -1,14 +1,10 @@
|
|||
const express = require('express');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const path = require('path');
|
||||
const { buildDependencies } = require('./dependencies');
|
||||
|
||||
function createApp(dependencies) {
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
|
||||
app.set('port', port);
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(express.json());
|
||||
|
|
@ -16,20 +12,20 @@ function createApp(dependencies) {
|
|||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
app.use(dependencies.middlewares.createSessionMiddleware);
|
||||
const createSessionMiddleware = require('./middlewares/sessionMiddleware');
|
||||
|
||||
app.use('/', dependencies.webRoutes);
|
||||
app.use('/api', dependencies.apiRoutes);
|
||||
app.use(createSessionMiddleware);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
const webRoutes = require('./routes/webRoutes');
|
||||
const apiRoutes = require('./routes/apiRoutes');
|
||||
|
||||
app.use('/', webRoutes);
|
||||
app.use('/api', apiRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.disable('etag'); //avoids 304 responses
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
const dependencies = buildDependencies();
|
||||
const app = createApp(dependencies);
|
||||
app.listen(app.get('port'), () => {
|
||||
console.log(`Server started on port ${app.get('port')}`);
|
||||
app.listen(port, () => {
|
||||
console.log(`Server started on port ${port}`);
|
||||
});
|
||||
|
|
|
|||
28
src/cli.js
|
|
@ -1,31 +1,13 @@
|
|||
const { Command } = require('commander');
|
||||
const { buildDependencies } = require('./dependencies');
|
||||
const program = new Command();
|
||||
|
||||
function buildCLIDependencies() {
|
||||
const appDependencies = buildDependencies();
|
||||
const createAppInviteCommand = require('./commands/createAppInvite');
|
||||
|
||||
const CreateAppInviteProvider = require('./commands/createAppInvite');
|
||||
const createAppInvite = new CreateAppInviteProvider({
|
||||
invitesService: appDependencies.services.invitesService,
|
||||
}).provide();
|
||||
program.version('1.0.0').description('CLI for managing web app tasks');
|
||||
|
||||
return { createAppInviteCommand: createAppInvite };
|
||||
}
|
||||
|
||||
function buildCLI({ createAppInviteCommand }) {
|
||||
const wipCli = new Command();
|
||||
wipCli.version('1.0.0').description('CLI for managing web app tasks');
|
||||
|
||||
wipCli
|
||||
program
|
||||
.command('createAppInvite <inviterNpub>')
|
||||
.description('Create an invite')
|
||||
.action(createAppInviteCommand);
|
||||
|
||||
return wipCli;
|
||||
}
|
||||
|
||||
const cliDependencies = buildCLIDependencies();
|
||||
|
||||
const cli = buildCLI(cliDependencies);
|
||||
|
||||
cli.parse(process.argv);
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
class CreateAppInviteProvider {
|
||||
constructor({ invitesService }) {
|
||||
this.invitesService = invitesService;
|
||||
}
|
||||
const invitesService = require('../services/invitesService');
|
||||
|
||||
provide() {
|
||||
const createAppInvite = async (inviterNpub) => {
|
||||
const appInvite = await this.invitesService.createAppInvite(inviterNpub);
|
||||
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}`);
|
||||
};
|
||||
|
||||
return createAppInvite;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CreateAppInviteProvider;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ const sequelize = new Sequelize({
|
|||
database: process.env.POSTGRES_DB,
|
||||
username: process.env.POSTGRES_USER,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
/* logging: (msg) => {
|
||||
logging: (msg) => {
|
||||
if (msg && (msg.includes('ERROR') || msg.includes('error'))) {
|
||||
console.error(msg);
|
||||
}
|
||||
}, */
|
||||
},
|
||||
define: {
|
||||
timestamps: false,
|
||||
freezeTableName: true,
|
||||
|
|
@ -23,4 +23,13 @@ const sequelize = new Sequelize({
|
|||
},
|
||||
});
|
||||
|
||||
sequelize
|
||||
.sync()
|
||||
.then(() => {
|
||||
console.log('Database synced');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error syncing the database:', err);
|
||||
});
|
||||
|
||||
module.exports = sequelize;
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -1,379 +0,0 @@
|
|||
'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');
|
||||
},
|
||||
};
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
'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');
|
||||
},
|
||||
};
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
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 };
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
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 = `<img src="/img/circle-check-white.svg" />
|
||||
<p>${this.text}</p>`;
|
||||
|
||||
this.element = div;
|
||||
this.parentElement.appendChild(div);
|
||||
}
|
||||
|
||||
display() {
|
||||
this.element.classList.add('revealed');
|
||||
}
|
||||
}
|
||||
module.exports = PopupNotification;
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
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 };
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
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();
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
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();
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
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: `<p>
|
||||
¡Atención! No se ha encontrado una extensión de Nostr en tu
|
||||
navegador. Puedes usar:
|
||||
</p>
|
||||
<p><strong>Firefox</strong></p>
|
||||
<p>
|
||||
<a
|
||||
href="https://addons.mozilla.org/en-US/firefox/addon/alby/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Alby</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://addons.mozilla.org/en-US/firefox/addon/nos2x-fox/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>nos2x-fox</a
|
||||
>
|
||||
</p>
|
||||
<p><strong>Chrome</strong></p>
|
||||
<p>
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/alby-bitcoin-wallet-for-l/iokeahhehimjnekafflcihljlcjccdbe?pli=1"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Alby</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>nos2x</a
|
||||
>
|
||||
</p>`,
|
||||
});
|
||||
noExtensionWarning.render();
|
||||
|
||||
const rejectedNostrWarning = new WarningDiv({
|
||||
parentElement: warningsContainer,
|
||||
id: 'rejected-nostr-nudges',
|
||||
innerHTML: `<p>
|
||||
Ups, parece que no has aceptado que usemos tus claves. Si te has
|
||||
equivocado, puedes intentarlo de nuevo.
|
||||
</p>`,
|
||||
});
|
||||
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();
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
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: `<p>
|
||||
Ups, esa clave no está registrada en la seca. ¿Quizás estás usando un
|
||||
perfil equivocado?
|
||||
</p>`,
|
||||
});
|
||||
notRegisteredPubkeyWarning.render();
|
||||
|
||||
const noExtensionWarning = new WarningDiv({
|
||||
parentElement: warningsArea,
|
||||
id: 'no-extension-nudges',
|
||||
innerHTML: `<p>
|
||||
¡Atención! No se ha encontrado una extensión de Nostr en tu navegador.
|
||||
Puedes usar:
|
||||
</p>
|
||||
<p><strong>Firefox</strong></p>
|
||||
<p>
|
||||
<a
|
||||
href="https://addons.mozilla.org/en-US/firefox/addon/alby/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Alby</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://addons.mozilla.org/en-US/firefox/addon/nos2x-fox/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>nos2x-fox</a
|
||||
>
|
||||
</p>
|
||||
<p><strong>Chrome</strong></p>
|
||||
<p>
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/alby-bitcoin-wallet-for-l/iokeahhehimjnekafflcihljlcjccdbe?pli=1"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Alby</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>nos2x</a
|
||||
>
|
||||
</p>`,
|
||||
});
|
||||
noExtensionWarning.render();
|
||||
|
||||
const nostrRejectedWarning = new WarningDiv({
|
||||
parentElement: warningsArea,
|
||||
id: 'rejected-nostr-nudges',
|
||||
innerHTML: `<p>
|
||||
Ups, parece que no has aceptado que usemos tus claves. Si te has
|
||||
equivocado, puedes intentarlo de nuevo.
|
||||
</p>`,
|
||||
});
|
||||
nostrRejectedWarning.render();
|
||||
|
||||
window.onload = () => {
|
||||
checkNostrExtension({
|
||||
window,
|
||||
successCallback: () => {
|
||||
console.log('Nostr extension present');
|
||||
},
|
||||
failureCallback: () => {
|
||||
console.log('Nostr extension not present');
|
||||
nostrLoginButton.disable();
|
||||
noExtensionWarning.display();
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
loginFunction();
|
||||
|
|
@ -1,694 +0,0 @@
|
|||
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 =
|
||||
'<p class="shadowed-round-area">Vaya, no hay nada por aquí...</p>';
|
||||
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();
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
function checkNostrExtension({ window, successCallback, failureCallback }) {
|
||||
if (!window.nostr) {
|
||||
failureCallback();
|
||||
} else {
|
||||
successCallback();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = checkNostrExtension;
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
class AttachPublicKeyMiddlewareProvider {
|
||||
constructor({ sessionService }) {
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
const sessionService = require('../services/sessionService');
|
||||
|
||||
provide() {
|
||||
return async (req, res, next) => {
|
||||
const publicKey = await this.sessionService.getPublicKeyRelatedToSession(
|
||||
async function attachPublicKeyMiddleware(req, res, next) {
|
||||
const publicKey = await sessionService.getPublicKeyRelatedToSession(
|
||||
req.cookies.sessionUuid
|
||||
);
|
||||
|
||||
|
|
@ -13,8 +9,6 @@ class AttachPublicKeyMiddlewareProvider {
|
|||
req.cookies.publicKey = publicKey;
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AttachPublicKeyMiddlewareProvider;
|
||||
module.exports = attachPublicKeyMiddleware;
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,18 +1,10 @@
|
|||
class RedirectHomeIfAuthorized {
|
||||
constructor({ sessionService }) {
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
const sessionService = require('../services/sessionService');
|
||||
|
||||
provide() {
|
||||
return async (req, res, next) => {
|
||||
if (
|
||||
await this.sessionService.isSessionAuthorized(req.cookies.sessionUuid)
|
||||
) {
|
||||
async function redirectHomeIfAuthorized(req, res, next) {
|
||||
if (await sessionService.isSessionAuthorized(req.cookies.sessionUuid)) {
|
||||
return res.redirect('/home');
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RedirectHomeIfAuthorized;
|
||||
module.exports = redirectHomeIfAuthorized;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
class RedirectIfMissingProfileDetailsMiddleware {
|
||||
constructor({ profileService }) {
|
||||
this.profileService = profileService;
|
||||
}
|
||||
const profileService = require('../services/profileService');
|
||||
|
||||
provide() {
|
||||
return async (req, res, next) => {
|
||||
async function redirectIfMissingProfileDetailsMiddleware(req, res, next) {
|
||||
const publicKey = req.cookies.publicKey;
|
||||
if (!(await this.profileService.areProfileDetailsComplete(publicKey))) {
|
||||
if (!(await profileService.areProfileDetailsComplete(publicKey))) {
|
||||
res.redirect('/createProfile');
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
module.exports = RedirectIfMissingProfileDetailsMiddleware;
|
||||
|
||||
module.exports = redirectIfMissingProfileDetailsMiddleware;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,10 @@
|
|||
class RedirectIfNotAuthorizedMiddleware {
|
||||
constructor({ sessionService }) {
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
const sessionService = require('../services/sessionService');
|
||||
|
||||
provide() {
|
||||
return async (req, res, next) => {
|
||||
if (
|
||||
!(await this.sessionService.isSessionAuthorized(
|
||||
req.cookies.sessionUuid
|
||||
))
|
||||
) {
|
||||
async function redirectIfNotAuthorizedMiddleware(req, res, next) {
|
||||
if (!(await sessionService.isSessionAuthorized(req.cookies.sessionUuid))) {
|
||||
return res.redirect('/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RedirectIfNotAuthorizedMiddleware;
|
||||
module.exports = redirectIfNotAuthorizedMiddleware;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,13 @@
|
|||
class RejectIfNotAuthorizedMiddleware {
|
||||
constructor({ sessionService }) {
|
||||
this.sessionService = sessionService;
|
||||
}
|
||||
const sessionService = require('../services/sessionService');
|
||||
|
||||
provide() {
|
||||
return async (req, res, next) => {
|
||||
if (
|
||||
!(await this.sessionService.isSessionAuthorized(
|
||||
req.cookies.sessionUuid
|
||||
))
|
||||
) {
|
||||
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.',
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RejectIfNotAuthorizedMiddleware;
|
||||
module.exports = rejectIfNotAuthorizedMiddleware;
|
||||
|
|
|
|||
33
src/middlewares/sessionMiddleware.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
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;
|
||||
|
|
@ -1,25 +1,21 @@
|
|||
class AppInviteCreatedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const AppInviteCreated = this.sequelize.define(
|
||||
const AppInviteCreated = sequelize.define(
|
||||
'AppInviteCreated',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
inviter_pub_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -27,8 +23,5 @@ class AppInviteCreatedProvider {
|
|||
tableName: 'app_invite_created',
|
||||
}
|
||||
);
|
||||
return AppInviteCreated;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppInviteCreatedProvider;
|
||||
module.exports = AppInviteCreated;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
class ContactDetailsSetProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const ContactDetailsSet = this.sequelize.define(
|
||||
const ContactDetailsSet = sequelize.define(
|
||||
'ContactDetailsSet',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
encrypted_contact_details: {
|
||||
type: this.DataTypes.TEXT,
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -31,8 +27,5 @@ class ContactDetailsSetProvider {
|
|||
tableName: 'contact_details_set',
|
||||
}
|
||||
);
|
||||
return ContactDetailsSet;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContactDetailsSetProvider;
|
||||
module.exports = ContactDetailsSet;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
class LoginChallengeCompletedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const LoginChallengeCompleted = this.sequelize.define(
|
||||
const LoginChallengeCompleted = sequelize.define(
|
||||
'LoginChallengeCompleted',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
nostr_challenge_completed_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -31,8 +27,5 @@ class LoginChallengeCompletedProvider {
|
|||
tableName: 'login_challenge_completed',
|
||||
}
|
||||
);
|
||||
return LoginChallengeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LoginChallengeCompletedProvider;
|
||||
module.exports = LoginChallengeCompleted;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
class LoginChallengeCreatedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const LoginChallengeCreated = this.sequelize.define(
|
||||
const LoginChallengeCreated = sequelize.define(
|
||||
'LoginChallengeCreated',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
nostr_challenge_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -27,8 +23,5 @@ class LoginChallengeCreatedProvider {
|
|||
tableName: 'login_challenge_created',
|
||||
}
|
||||
);
|
||||
return LoginChallengeCreated;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LoginChallengeCreatedProvider;
|
||||
module.exports = LoginChallengeCreated;
|
||||
|
|
|
|||
|
|
@ -1,34 +1,29 @@
|
|||
class NostrChallengeCompletedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const NostrChallengeCompleted = this.sequelize.define(
|
||||
const NostrChallengeCompleted = sequelize.define(
|
||||
'NostrChallengeCompleted',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
challenge: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
signed_event: {
|
||||
type: this.DataTypes.JSONB,
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -36,8 +31,5 @@ class NostrChallengeCompletedProvider {
|
|||
tableName: 'nostr_challenge_completed',
|
||||
}
|
||||
);
|
||||
return NostrChallengeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NostrChallengeCompletedProvider;
|
||||
module.exports = NostrChallengeCompleted;
|
||||
|
|
|
|||
|
|
@ -1,30 +1,25 @@
|
|||
class NostrChallengeCreatedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const NostrChallengeCreated = this.sequelize.define(
|
||||
const NostrChallengeCreated = sequelize.define(
|
||||
'NostrChallengeCreated',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
challenge: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
},
|
||||
expires_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -32,8 +27,5 @@ class NostrChallengeCreatedProvider {
|
|||
tableName: 'nostr_challenge_created',
|
||||
}
|
||||
);
|
||||
return NostrChallengeCreated;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NostrChallengeCreatedProvider;
|
||||
module.exports = NostrChallengeCreated;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
class NymSetProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const NymSet = this.sequelize.define(
|
||||
const NymSet = sequelize.define(
|
||||
'NymSet',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
nym: {
|
||||
type: this.DataTypes.TEXT,
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -31,8 +27,5 @@ class NymSetProvider {
|
|||
tableName: 'nym_set',
|
||||
}
|
||||
);
|
||||
return NymSet;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NymSetProvider;
|
||||
module.exports = NymSet;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
class OfferCreatedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const OfferCreated = this.sequelize.define(
|
||||
const OfferCreated = sequelize.define(
|
||||
'OfferCreated',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -27,8 +23,5 @@ class OfferCreatedProvider {
|
|||
tableName: 'offer_created',
|
||||
}
|
||||
);
|
||||
return OfferCreated;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferCreatedProvider;
|
||||
module.exports = OfferCreated;
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,69 +1,65 @@
|
|||
class OfferDetailsSetProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const OfferDetailsSet = this.sequelize.define(
|
||||
const OfferDetailsSet = sequelize.define(
|
||||
'OfferDetailsSet',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
offer_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
wants: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
premium: {
|
||||
type: this.DataTypes.DECIMAL(5, 2),
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
},
|
||||
trade_amount_eur: {
|
||||
type: this.DataTypes.INTEGER,
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
location_details: {
|
||||
type: this.DataTypes.TEXT,
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
time_availability_details: {
|
||||
type: this.DataTypes.TEXT,
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
show_offer_to_trusted: {
|
||||
type: this.DataTypes.BOOLEAN,
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
show_offer_to_trusted_trusted: {
|
||||
type: this.DataTypes.BOOLEAN,
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
show_offer_to_all_members: {
|
||||
type: this.DataTypes.BOOLEAN,
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
is_onchain_accepted: {
|
||||
type: this.DataTypes.BOOLEAN,
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
is_lightning_accepted: {
|
||||
type: this.DataTypes.BOOLEAN,
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
are_big_notes_accepted: {
|
||||
type: this.DataTypes.BOOLEAN,
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -71,8 +67,5 @@ class OfferDetailsSetProvider {
|
|||
tableName: 'offer_details_set',
|
||||
}
|
||||
);
|
||||
return OfferDetailsSet;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferDetailsSetProvider;
|
||||
module.exports = OfferDetailsSet;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
class SessionCreatedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const SessionCreated = this.sequelize.define(
|
||||
const SessionCreated = sequelize.define(
|
||||
'SessionCreated',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
expires_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -27,8 +23,5 @@ class SessionCreatedProvider {
|
|||
tableName: 'session_created',
|
||||
}
|
||||
);
|
||||
return SessionCreated;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SessionCreatedProvider;
|
||||
module.exports = SessionCreated;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
class SessionRelatedToPublickeyProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const SessionRelatedToPublickey = this.sequelize.define(
|
||||
const SessionRelatedToPublickey = sequelize.define(
|
||||
'SessionRelatedToPublickey',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
session_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -31,7 +27,5 @@ class SessionRelatedToPublickeyProvider {
|
|||
tableName: 'session_related_to_public_key',
|
||||
}
|
||||
);
|
||||
return SessionRelatedToPublickey;
|
||||
}
|
||||
}
|
||||
module.exports = SessionRelatedToPublickeyProvider;
|
||||
|
||||
module.exports = SessionRelatedToPublickey;
|
||||
|
|
|
|||
|
|
@ -1,33 +1,29 @@
|
|||
class SignUpChallengeCompletedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const SignUpChallengeCompleted = this.sequelize.define(
|
||||
const SignUpChallengeCompleted = sequelize.define(
|
||||
'SignUpChallengeCompleted',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
nostr_challenge_completed_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
app_invite_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
public_key: {
|
||||
type: this.DataTypes.STRING,
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -35,8 +31,5 @@ class SignUpChallengeCompletedProvider {
|
|||
tableName: 'sign_up_challenge_completed',
|
||||
}
|
||||
);
|
||||
return SignUpChallengeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SignUpChallengeCompletedProvider;
|
||||
module.exports = SignUpChallengeCompleted;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,25 @@
|
|||
class SignUpChallengeCreatedProvider {
|
||||
constructor({ sequelize, DataTypes }) {
|
||||
this.sequelize = sequelize;
|
||||
this.DataTypes = DataTypes;
|
||||
}
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../database');
|
||||
|
||||
provide() {
|
||||
const SignUpChallengeCreated = this.sequelize.define(
|
||||
const SignUpChallengeCreated = sequelize.define(
|
||||
'SignUpChallengeCreated',
|
||||
{
|
||||
uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
primaryKey: true,
|
||||
},
|
||||
nostr_challenge_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
app_invite_uuid: {
|
||||
type: this.DataTypes.UUID,
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
created_at: {
|
||||
type: this.DataTypes.DATE,
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
|
|
@ -32,8 +28,4 @@ class SignUpChallengeCreatedProvider {
|
|||
}
|
||||
);
|
||||
|
||||
return SignUpChallengeCreated;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SignUpChallengeCreatedProvider;
|
||||
module.exports = SignUpChallengeCreated;
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
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;
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
|
||||
.create-offer-step {
|
||||
width: 95%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
|
|
@ -62,8 +62,6 @@
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 70%;
|
||||
min-width: 500px;
|
||||
max-width: 95%;
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
|
|
@ -82,6 +80,15 @@
|
|||
margin: 10px 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#create-offer-root {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#view-my-offers-root {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#view-my-offers-root {
|
||||
|
|
@ -214,29 +221,24 @@
|
|||
.offer-action-area {
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.offer-long-text {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#create-offer-controls {
|
||||
text-align: center;
|
||||
overflow-y: auto;
|
||||
max-height: 800px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.create-offer-step {
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 13px #ccc;
|
||||
padding: 20px 0;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.create-offer-step h3 {
|
||||
|
|
@ -385,8 +387,3 @@
|
|||
#button-submit-offer {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
#close-offer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
}
|
||||
|
||||
.button-medium {
|
||||
height: 3em;
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
}
|
||||
|
||||
.button-medium {
|
||||
height: 3em;
|
||||
padding: 1em;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
|
@ -116,34 +118,6 @@ h1 {
|
|||
height: 50px;
|
||||
}
|
||||
|
||||
.full-screen-modal-background {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
transition: all 0.5s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.full-screen-modal-background.shown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.full-screen-modal {
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
width: fit-content;
|
||||
max-width: 95%;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
|
|
@ -223,19 +197,6 @@ h1 {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
background: white;
|
||||
border: 0;
|
||||
color: #e1c300;
|
||||
cursor: pointer;
|
||||
border: 3px solid #e1c300;
|
||||
transition: all 0.5 ease-in-out;
|
||||
}
|
||||
|
||||
.button-secondary:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button-large {
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
|
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 9 KiB After Width: | Height: | Size: 9 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 650 B After Width: | Height: | Size: 650 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
10
src/public/javascript/app.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const navbuttonHome = document.getElementById('navbutton-home');
|
||||
const navbuttonOffers = document.getElementById('navbutton-offers');
|
||||
|
||||
navbuttonHome.addEventListener('click', () => {
|
||||
window.location.href = '/home';
|
||||
});
|
||||
|
||||
navbuttonOffers.addEventListener('click', () => {
|
||||
window.location.href = '/offers';
|
||||
});
|
||||
121
src/public/javascript/createProfile.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
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);
|
||||