Create local business

This commit is contained in:
eraden 2022-07-06 22:24:59 +02:00
parent 7abf25345f
commit a727a78bcb
19 changed files with 312 additions and 127 deletions

114
client/dist/app.js vendored
View File

@ -8,6 +8,9 @@ form legend {
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 20px;
} }
form.inline div {
display: flex;
}
form > div { form > div {
display: block; display: block;
margin-bottom: 1rem; margin-bottom: 1rem;
@ -127,6 +130,9 @@ customElements.define("form-navigation", class extends HTMLElement {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
input {
max-width: 200px;
}
input.hidden { input.hidden {
display: none !important; display: none !important;
} }
@ -138,17 +144,9 @@ customElements.define("form-navigation", class extends HTMLElement {
</div> </div>
</form> </form>
`, c.querySelector("#prev").addEventListener("click", (a)=>{ `, c.querySelector("#prev").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.dispatchEvent(new CustomEvent("form:prev", { a.stopPropagation(), a.preventDefault(), this.prev();
bubbles: !0,
composed: !0,
detail: this.parentElement
}));
}), c.querySelector("#next").addEventListener("click", (a)=>{ }), c.querySelector("#next").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.dispatchEvent(new CustomEvent("form:next", { a.stopPropagation(), a.preventDefault(), this.next();
bubbles: !0,
composed: !0,
detail: this.parentElement
}));
}); });
} }
attributeChangedCallback(b, c, d) { attributeChangedCallback(b, c, d) {
@ -160,6 +158,20 @@ customElements.define("form-navigation", class extends HTMLElement {
this[S].querySelector("#prev").className = "hidden" === d ? "hidden" : ""; this[S].querySelector("#prev").className = "hidden" === d ? "hidden" : "";
} }
} }
next() {
this.dispatchEvent(new CustomEvent("form:next", {
bubbles: !0,
composed: !0,
detail: this.parentElement
}));
}
prev() {
this.dispatchEvent(new CustomEvent("form:prev", {
bubbles: !0,
composed: !0,
detail: this.parentElement
}));
}
}); });
customElements.define("local-service", class extends HTMLElement { customElements.define("local-service", class extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
@ -372,17 +384,19 @@ customElements.define("ow-account", class extends HTMLElement {
#form > * { #form > * {
display: none; display: none;
} }
:host([mode="login"]) login-form, :host([mode="login"]) #switch-register { :host([mode="login"]) #form > login-form, :host([mode="login"]) #switch-register {
display: block; display: block !important;
} }
:host([mode="register"]) register-form, :host([mode="register"]) #switch-login { :host([mode="register"]) #form > register-form, :host([mode="register"]) #switch-login {
display: block; display: block !important;
} }
#display { #display {
display: none; display: none;
} }
:host([mode="display"]) #display { display: block; } :host([mode="display"]) #display { display: block; }
:host([mode="display"]) #form { display: none; } :host([mode="display"]) #form { display: none; }
:host([mode="form"]) #form,
:host([mode="login"]) #form { display: block; }
a{ a{
display: block; display: block;
@ -406,6 +420,7 @@ customElements.define("ow-account", class extends HTMLElement {
<a>Masz konta? Zaloguj się</a> <a>Masz konta? Zaloguj się</a>
</section> </section>
</article> </article>
<article id="display"> <article id="display">
<div> <div>
<input id="id" name="id" readonly type="hidden" /> <input id="id" name="id" readonly type="hidden" />
@ -658,13 +673,20 @@ customElements.define("price-input", class extends HTMLElement {
${FORM_STYLE} ${FORM_STYLE}
</style> </style>
<div id="view"> <div id="view">
<input id="price" type="number" min="0.00" max="10000.00" step="0.01" /> <input
id="price" type="number" min="0.00" max="10000.00" step="0.01"
placeholder="Cena, np: 12.23"
/>
<span id="currency"></span> <span id="currency"></span>
</div> </div>
`; `;
let d = c.querySelector("#price");
d.addEventListener("change", (a)=>{
a.stopPropagation(), this.value = d.value;
});
} }
connectedCallback() { connectedCallback() {
this[S].querySelector("#currency").textContent = this.currency; this[S].querySelector("#currency").textContent = this.currency, this[S].querySelector("#price").value = this.value;
} }
attributeChangedCallback(b, c, d) { attributeChangedCallback(b, c, d) {
if (c === d) return; if (c === d) return;
@ -681,6 +703,9 @@ customElements.define("price-input", class extends HTMLElement {
break; break;
case "readonly": case "readonly":
d ? e.setAttribute("readonly", "readonly") : e.removeAttribute("readonly"); d ? e.setAttribute("readonly", "readonly") : e.removeAttribute("readonly");
break;
case "name":
this.setAttribute("name", d);
} }
} }
get value() { get value() {
@ -721,7 +746,7 @@ customElements.define("register-basic-form", class extends PseudoForm {
<form id="step-1"> <form id="step-1">
<div> <div>
<label>Login</label> <label>Login</label>
<input id="login" name="login" placeholder="Login" type="text" required /> <input id="login" name="login" placeholder="Login" type="text" required autofocus />
</div> </div>
<div> <div>
<label>E-Mail</label> <label>E-Mail</label>
@ -736,11 +761,7 @@ customElements.define("register-basic-form", class extends PseudoForm {
`; `;
let d = c.querySelector("form"); let d = c.querySelector("form");
d.addEventListener("submit", (a)=>{ d.addEventListener("submit", (a)=>{
a.preventDefault(), a.stopPropagation(), this.dispatchEvent(new CustomEvent("form:next", { a.preventDefault(), a.stopPropagation(), c.querySelector("form-navigation").next();
bubbles: !0,
composed: !0,
detail: d
}));
}); });
} }
}); });
@ -771,8 +792,13 @@ customElements.define("register-item-form-row", class extends PseudoForm {
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Noto Sans', sans-serif; }
${FORM_STYLE} ${FORM_STYLE}
section { form {
display: flex; display: flex;
justify-content: space-between;
}
div {
min-width: 100px;
max-width: 48%;
} }
</style> </style>
<form class="inline"> <form class="inline">
@ -801,8 +827,8 @@ customElements.define("register-item-form-row", class extends PseudoForm {
} }
get inputs() { get inputs() {
return [ return [
this[S].querySelector(".item-name").cloneNode(!0), d(this[S].querySelector(".item-name")),
this[S].querySelector(".item-price").cloneNode(!0), d(this[S].querySelector(".item-price")),
]; ];
} }
updateNames() { updateNames() {
@ -822,7 +848,11 @@ customElements.define("register-item-form-row", class extends PseudoForm {
return super.reportValidity() && this[S].querySelector("price-input").reportValidity(); return super.reportValidity() && this[S].querySelector("price-input").reportValidity();
} }
}); });
let d = (a)=>{ let d = ({ name: a , value: b })=>({
name: a,
value: b
});
let d1 = (a)=>{
let b = 0; let b = 0;
for (let c of a.querySelectorAll("register-item-form-row"))c.idx = b++; for (let c of a.querySelectorAll("register-item-form-row"))c.idx = b++;
return b; return b;
@ -856,11 +886,11 @@ customElements.define("register-items-form", class extends PseudoForm {
<form-navigation></form-navigation> <form-navigation></form-navigation>
</form> </form>
`, this.addEventListener("item:removed", (a)=>{ `, this.addEventListener("item:removed", (a)=>{
a.stopPropagation(), d(this); a.stopPropagation(), d1(this);
}), this.addEventListener("form:next", (a)=>{ }), this.addEventListener("form:next", (a)=>{
for (let b of this.querySelectorAll("item-form-row"))b.reportValidity() || (a.stopPropagation(), a.preventDefault()); for (let b of this.querySelectorAll("item-form-row"))b.reportValidity() || (a.stopPropagation(), a.preventDefault());
}), c.querySelector("#add-item").addEventListener("click", (a)=>{ }), c.querySelector("#add-item").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.appendChild(document.createElement("register-item-form-row")), d(this); a.stopPropagation(), a.preventDefault(), this.appendChild(document.createElement("register-item-form-row")), d1(this);
}); });
} }
get inputs() { get inputs() {
@ -883,7 +913,7 @@ customElements.define("register-company-form", class extends PseudoForm {
</style> </style>
<form id="step-2"> <form id="step-2">
<div> <div>
<input name="name" placeholder="Nazwa usługi" type="text" required /> <input name="name" placeholder="Nazwa usługi" type="text" required autofocus />
</div> </div>
<div> <div>
<label>description</label> <label>description</label>
@ -891,7 +921,9 @@ customElements.define("register-company-form", class extends PseudoForm {
</div> </div>
<form-navigation></form-navigation> <form-navigation></form-navigation>
</form> </form>
`; `, c.querySelector("form").addEventListener("submit", (a)=>{
a.preventDefault(), a.stopPropagation(), c.querySelector("form-navigation").next();
});
} }
}); });
customElements.define("register-submit-form", class extends PseudoForm { customElements.define("register-submit-form", class extends PseudoForm {
@ -905,12 +937,20 @@ customElements.define("register-submit-form", class extends PseudoForm {
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Noto Sans', sans-serif; }
${FORM_STYLE} ${FORM_STYLE}
.item-view {
display: flex;
justify-content: space-between;
}
.item-view > * {
min-width: 100px;
max-width: 48%;
}
</style> </style>
<form id="step-4"> <form id="step-4" method="post" action="/register">
<div id="copied"> <div id="copied">
<input id="hidden-login" name="login" type="hidden" /> <input id="hidden-login" name="login" type="hidden" />
<input id="hidden-email" name="email" type="hidden" /> <input id="hidden-email" name="email" type="hidden" />
<input id="hidden-pass" name="pass" type="hidden" /> <input id="hidden-pass" name="password" type="hidden" />
<input id="hidden-name" name="name" type="hidden" /> <input id="hidden-name" name="name" type="hidden" />
<input id="hidden-description" name="description" type="hidden" /> <input id="hidden-description" name="description" type="hidden" />
</div> </div>
@ -951,8 +991,14 @@ customElements.define("register-submit-form", class extends PseudoForm {
setItems(a) { setItems(a) {
let c = this[S].querySelector("#items"); let c = this[S].querySelector("#items");
for (let d of (c.innerHTML = "", a)){ for (let d of (c.innerHTML = "", a)){
let e = c.appendChild(document.createElement("div")), [f, g] = d; let e = c.appendChild(document.createElement("div"));
f.setAttribute("readonly", "readonly"), e.appendChild(f), e.appendChild(document.createElement("price-view")).value = g.value, g.setAttribute("readonly", "readonly"), g.setAttribute("type", "hidden"), e.appendChild(g); e.className = "item-view";
let [f, g] = d;
e.innerHTML = `
<input type="text" name="${f.name}" value="${f.value}" readonly />
<input type="hidden" name="${g.name}" value="${g.value}" readonly />
<price-view value="${g.value}"></price-view>
`;
} }
} }
set accountType(a) { set accountType(a) {

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,9 @@ customElements.define('form-navigation', class extends HTMLElement {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
input {
max-width: 200px;
}
input.hidden { input.hidden {
display: none !important; display: none !important;
} }
@ -32,20 +35,12 @@ customElements.define('form-navigation', class extends HTMLElement {
shadow.querySelector('#prev').addEventListener('click', ev => { shadow.querySelector('#prev').addEventListener('click', ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this.dispatchEvent(new CustomEvent('form:prev', { this.prev();
bubbles: true,
composed: true,
detail: this.parentElement
}));
}); });
shadow.querySelector('#next').addEventListener('click', ev => { shadow.querySelector('#next').addEventListener('click', ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this.dispatchEvent(new CustomEvent('form:next', { this.next();
bubbles: true,
composed: true,
detail: this.parentElement
}));
}); });
} }
@ -62,4 +57,20 @@ customElements.define('form-navigation', class extends HTMLElement {
} }
} }
} }
next() {
this.dispatchEvent(new CustomEvent('form:next', {
bubbles: true,
composed: true,
detail: this.parentElement
}));
}
prev() {
this.dispatchEvent(new CustomEvent('form:prev', {
bubbles: true,
composed: true,
detail: this.parentElement
}));
}
}); });

View File

@ -1,4 +1,4 @@
import { S } from "./shared"; import { S } from "../shared";
customElements.define('ow-nav', class extends HTMLElement { customElements.define('ow-nav', class extends HTMLElement {
constructor() { constructor() {

View File

@ -1,4 +1,4 @@
import { S } from "./shared"; import { S } from "../shared";
customElements.define('ow-path', class extends HTMLElement { customElements.define('ow-path', class extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {

View File

@ -15,17 +15,19 @@ customElements.define('ow-account', class extends HTMLElement {
#form > * { #form > * {
display: none; display: none;
} }
:host([mode="login"]) login-form, :host([mode="login"]) #switch-register { :host([mode="login"]) #form > login-form, :host([mode="login"]) #switch-register {
display: block; display: block !important;
} }
:host([mode="register"]) register-form, :host([mode="register"]) #switch-login { :host([mode="register"]) #form > register-form, :host([mode="register"]) #switch-login {
display: block; display: block !important;
} }
#display { #display {
display: none; display: none;
} }
:host([mode="display"]) #display { display: block; } :host([mode="display"]) #display { display: block; }
:host([mode="display"]) #form { display: none; } :host([mode="display"]) #form { display: none; }
:host([mode="form"]) #form,
:host([mode="login"]) #form { display: block; }
a{ a{
display: block; display: block;
@ -49,6 +51,7 @@ customElements.define('ow-account', class extends HTMLElement {
<a>Masz konta? Zaloguj się</a> <a>Masz konta? Zaloguj się</a>
</section> </section>
</article> </article>
<article id="display"> <article id="display">
<div> <div>
<input id="id" name="id" readonly type="hidden" /> <input id="id" name="id" readonly type="hidden" />

View File

@ -25,15 +25,24 @@ customElements.define('price-input', class extends HTMLElement {
${FORM_STYLE} ${FORM_STYLE}
</style> </style>
<div id="view"> <div id="view">
<input id="price" type="number" min="0.00" max="10000.00" step="0.01" /> <input
id="price" type="number" min="0.00" max="10000.00" step="0.01"
placeholder="Cena, np: 12.23"
/>
<span id="currency"></span> <span id="currency"></span>
</div> </div>
`; `;
const price = shadow.querySelector('#price');
price.addEventListener('change', ev => {
ev.stopPropagation();
this.value = price.value;
});
} }
connectedCallback() { connectedCallback() {
this[S].querySelector('#currency').textContent = this.currency; this[S].querySelector('#currency').textContent = this.currency;
// this[S].querySelector('#price').textContent = this.formatted; this[S].querySelector('#price').value = this.value;
} }
attributeChangedCallback(name, oldV, newV) { attributeChangedCallback(name, oldV, newV) {
@ -61,6 +70,7 @@ customElements.define('price-input', class extends HTMLElement {
break; break;
} }
case 'name': { case 'name': {
this.setAttribute('name', newV);
break; break;
} }
} }

View File

@ -16,7 +16,7 @@ customElements.define('register-basic-form', class extends PseudoForm {
<form id="step-1"> <form id="step-1">
<div> <div>
<label>Login</label> <label>Login</label>
<input id="login" name="login" placeholder="Login" type="text" required /> <input id="login" name="login" placeholder="Login" type="text" required autofocus />
</div> </div>
<div> <div>
<label>E-Mail</label> <label>E-Mail</label>
@ -34,7 +34,7 @@ customElements.define('register-basic-form', class extends PseudoForm {
form.addEventListener('submit', ev => { form.addEventListener('submit', ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.dispatchEvent(new CustomEvent('form:next', { bubbles: true, composed: true, detail: form })); shadow.querySelector('form-navigation').next();
}) })
} }
}); });

View File

@ -14,7 +14,7 @@ customElements.define('register-company-form', class extends PseudoForm {
</style> </style>
<form id="step-2"> <form id="step-2">
<div> <div>
<input name="name" placeholder="Nazwa usługi" type="text" required /> <input name="name" placeholder="Nazwa usługi" type="text" required autofocus />
</div> </div>
<div> <div>
<label>description</label> <label>description</label>
@ -23,5 +23,11 @@ customElements.define('register-company-form', class extends PseudoForm {
<form-navigation></form-navigation> <form-navigation></form-navigation>
</form> </form>
`; `;
shadow.querySelector('form').addEventListener('submit', ev => {
ev.preventDefault();
ev.stopPropagation();
shadow.querySelector('form-navigation').next();
});
} }
}) })

View File

@ -25,8 +25,13 @@ customElements.define('register-item-form-row', class extends PseudoForm {
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Noto Sans', sans-serif; }
${ FORM_STYLE } ${ FORM_STYLE }
section { form {
display: flex; display: flex;
justify-content: space-between;
}
div {
min-width: 100px;
max-width: 48%;
} }
</style> </style>
<form class="inline"> <form class="inline">
@ -65,11 +70,10 @@ customElements.define('register-item-form-row', class extends PseudoForm {
} }
} }
get inputs() { get inputs() {
return [ return [
this[S].querySelector('.item-name').cloneNode(true), extract(this[S].querySelector('.item-name')),
this[S].querySelector('.item-price').cloneNode(true), extract(this[S].querySelector('.item-price')),
]; ];
} }
@ -93,3 +97,5 @@ customElements.define('register-item-form-row', class extends PseudoForm {
return super.reportValidity() && this[S].querySelector('price-input').reportValidity(); return super.reportValidity() && this[S].querySelector('price-input').reportValidity();
} }
}); });
const extract = ({ name, value }) => ({ name, value })

View File

@ -11,12 +11,20 @@ customElements.define('register-submit-form', class extends PseudoForm {
:host { display: block; } :host { display: block; }
* { font-family: 'Noto Sans', sans-serif; } * { font-family: 'Noto Sans', sans-serif; }
${ FORM_STYLE } ${ FORM_STYLE }
.item-view {
display: flex;
justify-content: space-between;
}
.item-view > * {
min-width: 100px;
max-width: 48%;
}
</style> </style>
<form id="step-4"> <form id="step-4" method="post" action="/register">
<div id="copied"> <div id="copied">
<input id="hidden-login" name="login" type="hidden" /> <input id="hidden-login" name="login" type="hidden" />
<input id="hidden-email" name="email" type="hidden" /> <input id="hidden-email" name="email" type="hidden" />
<input id="hidden-pass" name="pass" type="hidden" /> <input id="hidden-pass" name="password" type="hidden" />
<input id="hidden-name" name="name" type="hidden" /> <input id="hidden-name" name="name" type="hidden" />
<input id="hidden-description" name="description" type="hidden" /> <input id="hidden-description" name="description" type="hidden" />
</div> </div>
@ -62,15 +70,14 @@ customElements.define('register-submit-form', class extends PseudoForm {
host.innerHTML = ``; host.innerHTML = ``;
for (const row of items) { for (const row of items) {
const el = host.appendChild(document.createElement('div')); const el = host.appendChild(document.createElement('div'));
el.className = 'item-view';
const [name, price] = row; const [name, price] = row;
name.setAttribute('readonly', 'readonly'); el.innerHTML = `
el.appendChild(name); <input type="text" name="${name.name}" value="${name.value}" readonly />
<input type="hidden" name="${price.name}" value="${price.value}" readonly />
el.appendChild(document.createElement('price-view')).value = price.value; <price-view value="${price.value}"></price-view>
price.setAttribute('readonly', 'readonly'); `;
price.setAttribute('type', 'hidden');
el.appendChild(price);
} }
} }

View File

@ -9,6 +9,9 @@ form legend {
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 20px;
} }
form.inline div {
display: flex;
}
form > div { form > div {
display: block; display: block;
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@ -1,49 +1,49 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE accounts ( CREATE TABLE accounts (
id serial unique not null primary key, id serial unique not null primary key,
login text not null, login text not null,
pass text not null pass text not null
); );
CREATE TYPE "Role" AS ENUM ( CREATE TYPE "Role" AS ENUM (
'User', 'User',
'Admin' 'Admin'
); );
CREATE TYPE "LocalServiceState" AS ENUM ( CREATE TYPE "LocalServiceState" AS ENUM (
'Pending', 'Pending',
'Approved', 'Approved',
'Banned', 'Banned',
'Pinned', 'Pinned',
'Internal' 'Internal'
); );
CREATE TABLE tokens ( CREATE TABLE tokens (
id serial unique not null primary key, id serial unique not null primary key,
claims jsonb not null, claims jsonb not null,
iss text not null default 'oswilno', /* issuer */ iss text not null default 'oswilno', /* issuer */
sub int references accounts (id), /* subject */ sub int references accounts (id), /* subject */
aud text not null default 'public', /* audience */ aud text not null default 'public', /* audience */
exp timestamp not null, /* expiration time */ exp timestamp not null, /* expiration time */
nbt timestamp not null default now(), /* not before time */ nbt timestamp not null default now(), /* not before time */
iat timestamp not null default now(), /* issued at time */ iat timestamp not null default now(), /* issued at time */
jti uuid not null unique, /* JWT ID - unique */ jti uuid not null unique, /* JWT ID - unique */
role "Role" not null default 'User' role "Role" not null default 'User'
); );
CREATE TABLE local_services ( CREATE TABLE local_services (
id serial unique not null primary key, id serial unique not null primary key,
owner_id int references accounts (id) not null, owner_id int references accounts (id) not null,
name text not null, name text not null,
description text not null, description text not null,
state "LocalServiceState" not null default 'Pending' state "LocalServiceState" not null default 'Pending'
); );
CREATE TABLE local_service_items ( CREATE TABLE local_service_items (
id serial unique not null primary key, id serial unique not null primary key,
local_service_id int references local_services (id) not null, local_service_id int references local_services (id) not null,
name text not null, name text not null,
price bigint not null, price bigint not null,
item_order int not null item_order int not null
); );

View File

@ -1 +1,2 @@
ALTER TABLE accounts ADD COLUMN email text not null default '' unique; ALTER TABLE accounts
ADD COLUMN email text not null default '' unique;

View File

@ -1 +1,2 @@
ALTER TABLE accounts ADD COLUMN facebook_id TEXT DEFAULT NULL; ALTER TABLE accounts
ADD COLUMN facebook_id TEXT DEFAULT NULL;

View File

@ -2,7 +2,7 @@
use crate::routes::render_index; use crate::routes::render_index;
use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{web, web::Data, App, HttpResponse, HttpServer}; use actix_web::{web, web::Data, App, HttpServer};
mod auth; mod auth;
mod model; mod model;

View File

@ -4,7 +4,7 @@ use sqlx::{FromRow, Type};
use std::collections::HashMap; use std::collections::HashMap;
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug, PartialOrd, PartialEq, Serialize, Deserialize, Type)] #[derive(Debug, PartialOrd, PartialEq, Copy, Clone, Serialize, Deserialize, Type)]
pub enum AccountType { pub enum AccountType {
User, User,
Business, Business,
@ -63,7 +63,7 @@ pub struct Token {
} }
#[derive(Debug, Serialize, Deserialize, FromRow)] #[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct LocalService { pub struct LocalBusiness {
pub id: i32, pub id: i32,
pub owner_id: i32, pub owner_id: i32,
pub name: String, pub name: String,
@ -72,7 +72,7 @@ pub struct LocalService {
} }
#[derive(Debug, Serialize, Deserialize, FromRow)] #[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct LocalServiceItem { pub struct LocalBusinessItem {
pub id: i32, pub id: i32,
pub local_service_id: i32, pub local_service_id: i32,
pub name: String, pub name: String,

View File

@ -36,6 +36,12 @@ impl Page {
} }
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct BusinessItemInput {
pub name: String,
pub price: u32,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct LocalService { pub struct LocalService {
pub id: i32, pub id: i32,
@ -43,11 +49,11 @@ pub struct LocalService {
pub name: String, pub name: String,
pub description: String, pub description: String,
pub state: db::LocalServiceState, pub state: db::LocalServiceState,
pub items: Vec<db::LocalServiceItem>, pub items: Vec<db::LocalBusinessItem>,
} }
impl<'v> From<(db::LocalService, &'v mut Vec<db::LocalServiceItem>)> for LocalService { impl<'v> From<(db::LocalBusiness, &'v mut Vec<db::LocalBusinessItem>)> for LocalService {
fn from((service, items): (db::LocalService, &'v mut Vec<db::LocalServiceItem>)) -> Self { fn from((service, items): (db::LocalBusiness, &'v mut Vec<db::LocalBusinessItem>)) -> Self {
Self { Self {
id: service.id, id: service.id,
owner_id: service.owner_id, owner_id: service.owner_id,

View File

@ -1,6 +1,6 @@
use crate::model::db; use crate::model::db;
use crate::model::db::AccountType; use crate::model::db::AccountType;
use crate::model::view::Page; use crate::model::view::{self, Page};
use crate::utils; use crate::utils;
use actix_files::Files; use actix_files::Files;
use actix_identity::Identity; use actix_identity::Identity;
@ -39,8 +39,8 @@ pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
_ => None, _ => None,
}; };
let (services, mut items) = { let (services, mut items) = {
use crate::model::db::{LocalService, LocalServiceItem}; use crate::model::db::{LocalBusiness, LocalBusinessItem};
let services: Vec<LocalService> = sqlx::query_as( let services: Vec<LocalBusiness> = sqlx::query_as(
r#" r#"
SELECT id, owner_id, name, description, state SELECT id, owner_id, name, description, state
FROM local_services FROM local_services
@ -58,7 +58,7 @@ ORDER BY id
}) })
.unwrap_or_default(); .unwrap_or_default();
let items: Vec<LocalServiceItem> = sqlx::query_as( let items: Vec<LocalBusinessItem> = sqlx::query_as(
r#" r#"
SELECT SELECT
id, id,
@ -132,6 +132,9 @@ struct RegisterForm {
password: String, password: String,
facebook_id: Option<String>, facebook_id: Option<String>,
account_type: db::AccountType, account_type: db::AccountType,
items: Option<Vec<view::BusinessItemInput>>,
name: Option<String>,
description: Option<String>,
} }
#[post("/register")] #[post("/register")]
@ -143,13 +146,17 @@ async fn register(
let form = form.into_inner(); let form = form.into_inner();
let pool = db.into_inner(); let pool = db.into_inner();
if form.account_type == AccountType::Admin { if form.account_type == AccountType::Admin {
return HttpResponse::BadRequest().body("Breach attempt detected!"); return HttpResponse::BadRequest().body("Security breach attempt detected!");
} }
let mut t = pool.begin().await.unwrap();
let pass = match utils::encrypt(&form.password) { let pass = match utils::encrypt(&form.password) {
Ok(pass) => pass, Ok(pass) => pass,
Err(e) => { Err(e) => {
tracing::error!("{:?}", e); tracing::error!("{:?}", e);
dbg!(e);
t.rollback().await.unwrap();
return HttpResponse::BadRequest().body( return HttpResponse::BadRequest().body(
AccountTemplate { AccountTemplate {
account: None, account: None,
@ -165,7 +172,7 @@ async fn register(
let res: sqlx::Result<db::Account> = sqlx::query_as( let res: sqlx::Result<db::Account> = sqlx::query_as(
r#" r#"
INSERT INTO accounts (login, email, pass, facebook_id, account_type) INSERT INTO accounts (login, email, pass, facebook_id, account_type)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4, $5)
RETURNING id, login, email, pass, facebook_id, account_type RETURNING id, login, email, pass, facebook_id, account_type
"#, "#,
) )
@ -173,24 +180,19 @@ RETURNING id, login, email, pass, facebook_id, account_type
.bind(form.email) .bind(form.email)
.bind(pass) .bind(pass)
.bind(form.facebook_id) .bind(form.facebook_id)
.fetch_one(&*pool) .bind(form.account_type)
.fetch_one(&mut t)
.await; .await;
match res { let account = match res {
Ok(res) => { Ok(res) => {
id.remember(format!("{}", res.id)); id.remember(format!("{}", res.id));
HttpResponse::Ok().body( res
AccountTemplate {
account: Some(res),
error: None,
page: Page::Register,
}
.render()
.unwrap(),
)
} }
Err(e) => { Err(e) => {
eprintln!("{e}"); tracing::error!("{e}");
HttpResponse::BadRequest().body( dbg!(e);
t.rollback().await.unwrap();
return HttpResponse::BadRequest().body(
AccountTemplate { AccountTemplate {
account: None, account: None,
error: Some("Problem z utworzeniem konta".into()), error: Some("Problem z utworzeniem konta".into()),
@ -198,9 +200,92 @@ RETURNING id, login, email, pass, facebook_id, account_type
} }
.render() .render()
.unwrap(), .unwrap(),
);
}
};
if matches!(form.account_type, AccountType::Business) {
let name = form.name.as_deref().unwrap_or_default();
let owner_id = account.id;
let description = form.description.as_deref().unwrap_or_default();
let res: sqlx::Result<db::LocalBusiness> = sqlx::query_as(
r#"
INSERT INTO local_services (name, owner_id, description)
VALUES ($1, $2, $3)
RETURNING id, owner_id, name, description, state
"#,
)
.bind(name)
.bind(owner_id)
.bind(description)
.fetch_one(&mut t)
.await;
let business = match res {
Ok(business) => business,
Err(e) => {
tracing::error!("{e}");
dbg!(e);
t.rollback().await.unwrap();
return HttpResponse::BadRequest().body(
AccountTemplate {
account: None,
error: Some("Problem z utworzeniem konta".into()),
page: Page::Register,
}
.render()
.unwrap(),
);
}
};
for (idx, item) in form.items.as_deref().unwrap_or_default().iter().enumerate() {
let res: sqlx::Result<db::LocalBusinessItem> = sqlx::query_as(
r#"
INSERT INTO local_service_items (local_service_id, name, price, item_order)
VALUES ($1, $2, $3, $4)
RETURNING id, local_service_id, name, price, item_order
"#,
) )
.bind(business.id)
.bind(&item.name)
.bind(item.price as i32)
.bind(idx as i32)
.fetch_one(&mut t)
.await;
match res {
Ok(_) => {}
Err(e) => {
tracing::error!("{e}");
dbg!(e);
t.rollback().await.unwrap();
return HttpResponse::BadRequest().body(
AccountTemplate {
account: None,
error: Some("Problem z utworzeniem konta".into()),
page: Page::Register,
}
.render()
.unwrap(),
);
}
}
} }
} }
t.commit().await.unwrap();
HttpResponse::SeeOther()
.append_header(("Location", "/"))
.body(
AccountTemplate {
account: Some(account),
error: None,
page: Page::Register,
}
.render()
.unwrap(),
)
} }
#[post("/logout")] #[post("/logout")]