Rename data table, start owned business items
This commit is contained in:
parent
a727a78bcb
commit
4ce82d15fb
152
Cargo.lock
generated
152
Cargo.lock
generated
@ -2,29 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "actix"
|
|
||||||
version = "0.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3720d0064a0ce5c0de7bd93bdb0a6caebab2a9b5668746145d7b3b0c5da02914"
|
|
||||||
dependencies = [
|
|
||||||
"actix-rt",
|
|
||||||
"bitflags",
|
|
||||||
"bytes",
|
|
||||||
"crossbeam-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
"log",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot 0.11.2",
|
|
||||||
"pin-project-lite",
|
|
||||||
"smallvec",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util 0.6.10",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix"
|
name = "actix"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -46,7 +23,7 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util 0.7.3",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -63,7 +40,7 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util 0.7.3",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -106,9 +83,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "3.1.0"
|
version = "3.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd2e9f6794b5826aff6df65e3a0d0127b271d1c03629c774238f3582e903d4e4"
|
checksum = "6f9ffb6db08c1c3a1f4aef540f1a63193adc73c4fbd40b75a95fc8c5258f6e51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-codec",
|
"actix-codec",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
@ -166,27 +143,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "actix-redis"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5dde9fa8bde15d084d459eb59f766c08d00a6f550e7054187878fc9cbaa19115"
|
|
||||||
dependencies = [
|
|
||||||
"actix 0.12.0",
|
|
||||||
"actix-rt",
|
|
||||||
"actix-service",
|
|
||||||
"actix-tls",
|
|
||||||
"actix-web",
|
|
||||||
"backoff",
|
|
||||||
"derive_more",
|
|
||||||
"futures-core",
|
|
||||||
"log",
|
|
||||||
"redis-async",
|
|
||||||
"time 0.3.11",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util 0.6.10",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -241,22 +197,6 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "actix-tls"
|
|
||||||
version = "3.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297"
|
|
||||||
dependencies = [
|
|
||||||
"actix-codec",
|
|
||||||
"actix-rt",
|
|
||||||
"actix-service",
|
|
||||||
"actix-utils",
|
|
||||||
"futures-core",
|
|
||||||
"log",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio-util 0.7.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-utils"
|
name = "actix-utils"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@ -489,17 +429,6 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "backoff"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
"instant",
|
|
||||||
"rand",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -831,6 +760,21 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@ -847,6 +791,17 @@ version = "0.3.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-intrusive"
|
name = "futures-intrusive"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -858,6 +813,12 @@ dependencies = [
|
|||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@ -887,10 +848,13 @@ version = "0.3.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
@ -962,7 +926,7 @@ dependencies = [
|
|||||||
"indexmap",
|
"indexmap",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util 0.7.3",
|
"tokio-util",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1313,16 +1277,19 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||||||
name = "oswilno"
|
name = "oswilno"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix 0.13.0",
|
"actix",
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
"actix-files",
|
"actix-files",
|
||||||
|
"actix-http",
|
||||||
"actix-identity",
|
"actix-identity",
|
||||||
"actix-redis",
|
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
|
"actix-utils",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"argon2",
|
"argon2",
|
||||||
"askama",
|
"askama",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"futures",
|
||||||
|
"futures-util",
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
"password-hash",
|
"password-hash",
|
||||||
"rand",
|
"rand",
|
||||||
@ -1529,21 +1496,6 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redis-async"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76b00c604527d485d7a146d1e324ec1cf0a5ec522acb3d05bf7d51a9c28d7c0c"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-channel",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-util",
|
|
||||||
"log",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util 0.6.10",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@ -2023,20 +1975,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-util"
|
|
||||||
version = "0.6.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"log",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
@ -6,10 +6,11 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
actix = { version = "*" }
|
actix = { version = "*" }
|
||||||
actix-web = { version = "*" }
|
actix-web = { version = "*" }
|
||||||
|
actix-http = { version = "3.2.1" }
|
||||||
actix-cors = { version = "*" }
|
actix-cors = { version = "*" }
|
||||||
actix-rt = { version = "*" }
|
actix-rt = { version = "*" }
|
||||||
|
actix-utils = { version = "3.0.0" }
|
||||||
actix-files = { version = "*" }
|
actix-files = { version = "*" }
|
||||||
actix-redis = { version = "0.11.0" }
|
|
||||||
actix-identity = { version = "0.4.0" }
|
actix-identity = { version = "0.4.0" }
|
||||||
askama = { version = "*" }
|
askama = { version = "*" }
|
||||||
validator = { version = "0.14", features = ["derive"] }
|
validator = { version = "0.14", features = ["derive"] }
|
||||||
@ -25,3 +26,5 @@ tracing-actix-web = { version = "*" }
|
|||||||
argon2 = { version = "0.4.1" }
|
argon2 = { version = "0.4.1" }
|
||||||
password-hash = { version = "0.4.2" }
|
password-hash = { version = "0.4.2" }
|
||||||
rand = { version = "0.8.5", features = [] }
|
rand = { version = "0.8.5", features = [] }
|
||||||
|
futures = { version = "0.3.21", features = ["async-await", "std"] }
|
||||||
|
futures-util = { version = "0.3.21", features = [] }
|
||||||
|
@ -17,11 +17,21 @@
|
|||||||
<h1>OS Wilno</h1>
|
<h1>OS Wilno</h1>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
{% match error %}
|
||||||
|
{% when Some with (e) %}
|
||||||
|
<p class="error">{{e}}></p>
|
||||||
|
{% when None %}
|
||||||
|
{% endmatch %}
|
||||||
<article>
|
<article>
|
||||||
<ow-nav>
|
<ow-nav>
|
||||||
<ow-path path="/" selected="{{ page.select_index() }}">Lokalne Usługi</ow-path>
|
<ow-path path="/" selected="{{ page.select_index() }}">Lokalne Usługi</ow-path>
|
||||||
<ow-path path="/news" selected="{{ page.select_news() }}">Aktualności</ow-path>
|
<ow-path path="/news" selected="{{ page.select_news() }}">Aktualności</ow-path>
|
||||||
<ow-path path="/account" selected="{{ page.select_account() }}">Konto</ow-path>
|
<ow-path path="/account" selected="{{ page.select_account() }}">Konto</ow-path>
|
||||||
|
{% match account.as_ref() %}
|
||||||
|
{% when Some with (a) %}
|
||||||
|
<ow-path path="/account/business-items" selected="{{ page.select_business_items() }}">Moje usługi</ow-path>
|
||||||
|
{% when None %}
|
||||||
|
{% endmatch %}
|
||||||
</ow-nav>
|
</ow-nav>
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</article>
|
</article>
|
||||||
|
10
assets/templates/business-items.html
Normal file
10
assets/templates/business-items.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<business-items>
|
||||||
|
{% for item in items %}
|
||||||
|
<business-item name="{{item.name}}" price="{{item.price}}">
|
||||||
|
</business-item>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</business-items>
|
||||||
|
{% endblock %}
|
@ -1,8 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<local-services>
|
<local-businesses>
|
||||||
{% for service in services %}
|
{% for service in services %}
|
||||||
<local-service
|
<local-business
|
||||||
slot="services"
|
slot="services"
|
||||||
service-id="{{service.id}}"
|
service-id="{{service.id}}"
|
||||||
name="{{service.name}}"
|
name="{{service.name}}"
|
||||||
@ -13,11 +13,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for item in service.items %}
|
{% for item in service.items %}
|
||||||
<local-service-item slot="item" name="{{item.name}}" price="{{item.price}}">
|
<local-business-item slot="item" name="{{item.name}}" price="{{item.price}}">
|
||||||
</local-service-item>
|
</local-business-item>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</local-service>
|
</local-business>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</local-services>
|
</local-businesses>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
187
client/dist/app.js
vendored
187
client/dist/app.js
vendored
@ -92,6 +92,10 @@ input[type="button"], input[type="submit"] {
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: calc(1.5em + 0.75rem + 2px);
|
height: calc(1.5em + 0.75rem + 2px);
|
||||||
padding: .375rem .75rem;
|
padding: .375rem .75rem;
|
||||||
|
|
||||||
|
border: 1px solid #495057;
|
||||||
|
color: #495057;
|
||||||
|
background: white;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
class PseudoForm extends HTMLElement {
|
class PseudoForm extends HTMLElement {
|
||||||
@ -173,45 +177,7 @@ customElements.define("form-navigation", class extends HTMLElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
customElements.define("local-service", class extends HTMLElement {
|
customElements.define("local-business-item", class extends HTMLElement {
|
||||||
static get observedAttributes() {
|
|
||||||
return [
|
|
||||||
"name",
|
|
||||||
"service-id",
|
|
||||||
"state"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
constructor(){
|
|
||||||
super();
|
|
||||||
let b = this[S] = this.attachShadow({
|
|
||||||
mode: "closed"
|
|
||||||
});
|
|
||||||
b.innerHTML = `
|
|
||||||
<style>
|
|
||||||
:host { display: block; }
|
|
||||||
* { font-family: 'Noto Sans', sans-serif; }
|
|
||||||
#items {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<h2 id="name"></h2>
|
|
||||||
<slot name="description"></slot>
|
|
||||||
<section id="items">
|
|
||||||
<slot name="item"></slot>
|
|
||||||
</section>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
connectedCallback() {
|
|
||||||
this[S].querySelector("#name").textContent = this.getAttribute("name");
|
|
||||||
}
|
|
||||||
attributeChangedCallback(b, c, d) {
|
|
||||||
if (c !== d && "name" === b) return this[S].querySelector("#name").textContent = d;
|
|
||||||
}
|
|
||||||
get name() {
|
|
||||||
return this.getAttribute("name") || "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
customElements.define("local-service-item", class extends HTMLElement {
|
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return [
|
return [
|
||||||
"name",
|
"name",
|
||||||
@ -260,7 +226,45 @@ customElements.define("local-service-item", class extends HTMLElement {
|
|||||||
return isNaN(b) ? 0 : b;
|
return isNaN(b) ? 0 : b;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
customElements.define("local-services", class extends HTMLElement {
|
customElements.define("local-business", class extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return [
|
||||||
|
"name",
|
||||||
|
"service-id",
|
||||||
|
"state"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
let b = this[S] = this.attachShadow({
|
||||||
|
mode: "closed"
|
||||||
|
});
|
||||||
|
b.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; }
|
||||||
|
* { font-family: 'Noto Sans', sans-serif; }
|
||||||
|
#items {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h2 id="name"></h2>
|
||||||
|
<slot name="description"></slot>
|
||||||
|
<section id="items">
|
||||||
|
<slot name="item"></slot>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this[S].querySelector("#name").textContent = this.getAttribute("name");
|
||||||
|
}
|
||||||
|
attributeChangedCallback(b, c, d) {
|
||||||
|
if (c !== d && "name" === b) return this[S].querySelector("#name").textContent = d;
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.getAttribute("name") || "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
customElements.define("local-businesses", class extends HTMLElement {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return [
|
return [
|
||||||
"filter"
|
"filter"
|
||||||
@ -899,7 +903,7 @@ customElements.define("register-items-form", class extends PseudoForm {
|
|||||||
].map((a)=>a.inputs);
|
].map((a)=>a.inputs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
customElements.define("register-company-form", class extends PseudoForm {
|
customElements.define("register-business-form", class extends PseudoForm {
|
||||||
constructor(){
|
constructor(){
|
||||||
super();
|
super();
|
||||||
let c = this[S] = this.attachShadow({
|
let c = this[S] = this.attachShadow({
|
||||||
@ -1226,7 +1230,7 @@ customElements.define("register-form", class extends HTMLElement {
|
|||||||
<article>
|
<article>
|
||||||
<register-user-type id="step-0"> </register-user-type>
|
<register-user-type id="step-0"> </register-user-type>
|
||||||
<register-basic-form id="step-1"></register-basic-form>
|
<register-basic-form id="step-1"></register-basic-form>
|
||||||
<register-company-form id="step-2"></register-company-form>
|
<register-business-form id="step-2"></register-business-form>
|
||||||
<register-items-form id="step-3"></register-items-form>
|
<register-items-form id="step-3"></register-items-form>
|
||||||
<register-submit-form id="step-4"></register-submit-form>
|
<register-submit-form id="step-4"></register-submit-form>
|
||||||
<register-user-form id="step-40"></register-user-form>
|
<register-user-form id="step-40"></register-user-form>
|
||||||
@ -1261,6 +1265,105 @@ customElements.define("register-form", class extends HTMLElement {
|
|||||||
a < 0 || this.setAttribute("step", a);
|
a < 0 || this.setAttribute("step", a);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
customElements.define("image-input", class extends HTMLElement {
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
let b = this[S] = this.attachShadow({
|
||||||
|
mode: "closed"
|
||||||
|
});
|
||||||
|
b.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; border: 1px solid black; }
|
||||||
|
#hidden { overflow: hidden; width: 1px; height: 1px; position: relative; }
|
||||||
|
input { position: absolute; top: -10px; left: -10px; display: none; }
|
||||||
|
#view { width: 200px; height: 200px; cursor: pointer; }
|
||||||
|
canvas { width: 200px; height: 200px; }
|
||||||
|
</style>
|
||||||
|
<article>
|
||||||
|
<section id="hidden">
|
||||||
|
<input id="file" type="file" accept="image/*" />
|
||||||
|
<img alt="" src="" />
|
||||||
|
</section>
|
||||||
|
<div id="view"><canvas width="200" height="200"></canvas></div>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
let c = new FileReader(), d = b.querySelector("#file"), e = b.querySelector("#view"), f = b.querySelector("img"), g = b.querySelector("canvas"), h = g.getContext("2d");
|
||||||
|
f.addEventListener("load", ()=>{
|
||||||
|
let a, b;
|
||||||
|
f.width > f.height ? (a = 200, b = 200 * f.height / f.width) : (a = 200 * f.width / f.height, b = 200), console.log(f.width, f.height), console.log(a, b), f.width = a, f.height = b, h.fillStyle = "#F00", h.rect(0, 0, 200, 200), h.drawImage(f, 0, 0, a, b);
|
||||||
|
}), d.addEventListener("change", (a)=>{
|
||||||
|
a.stopPropagation(), c.addEventListener("loadend", (a)=>{
|
||||||
|
a.total === a.loaded && (f.src = a.target.result || "");
|
||||||
|
}), c.readAsDataURL(a.target.files[0]);
|
||||||
|
}), e.addEventListener("click", (a)=>{
|
||||||
|
a.stopPropagation(), d.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
customElements.define("business-item", class extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return [
|
||||||
|
"name",
|
||||||
|
"price",
|
||||||
|
"picture-url"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
let b = this[S] = this.attachShadow({
|
||||||
|
mode: "closed"
|
||||||
|
});
|
||||||
|
b.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; }
|
||||||
|
section { display: flex; justify-content: space-between; }
|
||||||
|
</style>
|
||||||
|
<section>
|
||||||
|
<div id="name"></div>
|
||||||
|
<price-input></price-input>
|
||||||
|
<image-input></image-input>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
connectedCallback() {
|
||||||
|
this.filter = this.getAttribute("filter");
|
||||||
|
}
|
||||||
|
attributeChangedCallback(a, b, c) {
|
||||||
|
if (b !== c && "filter" === a) return this.filter = c;
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.getAttribute("name");
|
||||||
|
}
|
||||||
|
set name(a) {
|
||||||
|
this.setAttribute("name", a), this.querySelector("#name").textContent = a;
|
||||||
|
}
|
||||||
|
get price() {
|
||||||
|
return this.getAttribute("price");
|
||||||
|
}
|
||||||
|
set price(a) {
|
||||||
|
this.setAttribute("price", a), this.querySelector("price-input").value = a;
|
||||||
|
}
|
||||||
|
get picture_url() {
|
||||||
|
return this.getAttribute("picture-url");
|
||||||
|
}
|
||||||
|
set picture_url(a) {
|
||||||
|
this.setAttribute("picture-url", a), this.querySelector("image-input").src = a;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
customElements.define("business-items", class extends HTMLElement {
|
||||||
|
constructor(){
|
||||||
|
super();
|
||||||
|
let b = this[S] = this.attachShadow({
|
||||||
|
mode: "closed"
|
||||||
|
});
|
||||||
|
b.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; }
|
||||||
|
</style>
|
||||||
|
<slot></slot>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
if (!document.querySelector("#facebook-jssdk")) {
|
if (!document.querySelector("#facebook-jssdk")) {
|
||||||
window.fbAsyncInit = ()=>{
|
window.fbAsyncInit = ()=>{
|
||||||
FB.init({
|
FB.init({
|
||||||
|
2
client/dist/app.js.map
vendored
2
client/dist/app.js.map
vendored
File diff suppressed because one or more lines are too long
@ -1,7 +1,5 @@
|
|||||||
import "./form-navigation.js";
|
import "./form-navigation.js";
|
||||||
import "./local-service.js";
|
import "./local-businesses.js";
|
||||||
import "./local-service-item.js";
|
|
||||||
import "./local-services.js";
|
|
||||||
import "./login-form.js";
|
import "./login-form.js";
|
||||||
import "./ow-account.js";
|
import "./ow-account.js";
|
||||||
import "./nav/ow-nav.js";
|
import "./nav/ow-nav.js";
|
||||||
@ -9,6 +7,8 @@ import "./nav/ow-path.js";
|
|||||||
import "./price/price-view";
|
import "./price/price-view";
|
||||||
import "./price/price-input";
|
import "./price/price-input";
|
||||||
import "./register-form.js";
|
import "./register-form.js";
|
||||||
|
import "./business-items";
|
||||||
|
|
||||||
import { fireFbReady } from "./shared.js";
|
import { fireFbReady } from "./shared.js";
|
||||||
|
|
||||||
if (!document.querySelector('#facebook-jssdk')) {
|
if (!document.querySelector('#facebook-jssdk')) {
|
||||||
|
18
client/src/business-items.js
Normal file
18
client/src/business-items.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { S } from "./shared"
|
||||||
|
|
||||||
|
import "./business-items/business-item";
|
||||||
|
|
||||||
|
customElements.define('business-items', class extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const shadow = this[S] = this.attachShadow({ mode: "closed" });
|
||||||
|
|
||||||
|
shadow.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; }
|
||||||
|
</style>
|
||||||
|
<slot></slot>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
64
client/src/business-items/business-item.js
Normal file
64
client/src/business-items/business-item.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { S } from "../shared.js";
|
||||||
|
import "../shared/image-input";
|
||||||
|
|
||||||
|
customElements.define('business-item', class extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['name', 'price', 'picture-url']
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const shadow = this[S] = this.attachShadow({ mode: "closed" });
|
||||||
|
|
||||||
|
shadow.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; }
|
||||||
|
section { display: flex; justify-content: space-between; }
|
||||||
|
</style>
|
||||||
|
<section>
|
||||||
|
<div id="name"></div>
|
||||||
|
<price-input></price-input>
|
||||||
|
<image-input></image-input>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.filter = this.getAttribute('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldV, newV) {
|
||||||
|
if (oldV === newV) return;
|
||||||
|
switch (name) {
|
||||||
|
case 'filter':
|
||||||
|
return this.filter = newV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.getAttribute('name');
|
||||||
|
}
|
||||||
|
set name(v) {
|
||||||
|
this.setAttribute('name', v);
|
||||||
|
this.querySelector('#name').textContent = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get price() {
|
||||||
|
return this.getAttribute('price');
|
||||||
|
}
|
||||||
|
set price(v) {
|
||||||
|
this.setAttribute('price', v);
|
||||||
|
this.querySelector('price-input').value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get picture_url() {
|
||||||
|
return this.getAttribute('picture-url');
|
||||||
|
}
|
||||||
|
set picture_url(v) {
|
||||||
|
this.setAttribute('picture-url', v);
|
||||||
|
this.querySelector('image-input').src = v;
|
||||||
|
}
|
||||||
|
});
|
@ -1,6 +1,9 @@
|
|||||||
import { S } from "./shared";
|
import { S } from "./shared";
|
||||||
|
|
||||||
customElements.define('local-services', class extends HTMLElement {
|
import "./local-businesses/local-business-item";
|
||||||
|
import "./local-businesses/local-business";
|
||||||
|
|
||||||
|
customElements.define('local-businesses', class extends HTMLElement {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['filter']
|
return ['filter']
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { S } from "./shared";
|
import { S } from "../shared";
|
||||||
|
|
||||||
customElements.define('local-service-item', class extends HTMLElement {
|
customElements.define('local-business-item', class extends HTMLElement {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['name', 'price']
|
return ['name', 'price']
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { S } from "./shared";
|
import { S } from "../shared";
|
||||||
|
|
||||||
customElements.define('local-service', class extends HTMLElement {
|
customElements.define('local-business', class extends HTMLElement {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['name', 'service-id', 'state']
|
return ['name', 'service-id', 'state']
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ import { S, FORM_STYLE } from "./shared";
|
|||||||
import "./register-form/register-basic-form";
|
import "./register-form/register-basic-form";
|
||||||
import "./register-form/register-item-form-row.js";
|
import "./register-form/register-item-form-row.js";
|
||||||
import "./register-form/register-items-form.js";
|
import "./register-form/register-items-form.js";
|
||||||
import "./register-form/register-company-form";
|
import "./register-form/register-business-form";
|
||||||
import "./register-form/register-submit-form";
|
import "./register-form/register-submit-form";
|
||||||
import "./register-form/register-user-type";
|
import "./register-form/register-user-type";
|
||||||
import "./register-form/register-user-form";
|
import "./register-form/register-user-form";
|
||||||
@ -69,7 +69,7 @@ customElements.define('register-form', class extends HTMLElement {
|
|||||||
<article>
|
<article>
|
||||||
<register-user-type id="step-0"> </register-user-type>
|
<register-user-type id="step-0"> </register-user-type>
|
||||||
<register-basic-form id="step-1"></register-basic-form>
|
<register-basic-form id="step-1"></register-basic-form>
|
||||||
<register-company-form id="step-2"></register-company-form>
|
<register-business-form id="step-2"></register-business-form>
|
||||||
<register-items-form id="step-3"></register-items-form>
|
<register-items-form id="step-3"></register-items-form>
|
||||||
<register-submit-form id="step-4"></register-submit-form>
|
<register-submit-form id="step-4"></register-submit-form>
|
||||||
<register-user-form id="step-40"></register-user-form>
|
<register-user-form id="step-40"></register-user-form>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FORM_STYLE, S, PseudoForm } from "../shared";
|
import { FORM_STYLE, S, PseudoForm } from "../shared";
|
||||||
|
|
||||||
customElements.define('register-company-form', class extends PseudoForm {
|
customElements.define('register-business-form', class extends PseudoForm {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -93,6 +93,10 @@ input[type="button"], input[type="submit"] {
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: calc(1.5em + 0.75rem + 2px);
|
height: calc(1.5em + 0.75rem + 2px);
|
||||||
padding: .375rem .75rem;
|
padding: .375rem .75rem;
|
||||||
|
|
||||||
|
border: 1px solid #495057;
|
||||||
|
color: #495057;
|
||||||
|
background: white;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
67
client/src/shared/image-input.js
Normal file
67
client/src/shared/image-input.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { S } from "../shared.js";
|
||||||
|
|
||||||
|
customElements.define('image-input', class extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const shadow = this[S] = this.attachShadow({ mode: "closed" });
|
||||||
|
|
||||||
|
shadow.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { display: block; border: 1px solid black; }
|
||||||
|
#hidden { overflow: hidden; width: 1px; height: 1px; position: relative; }
|
||||||
|
input { position: absolute; top: -10px; left: -10px; display: none; }
|
||||||
|
#view { width: 200px; height: 200px; cursor: pointer; }
|
||||||
|
canvas { width: 200px; height: 200px; }
|
||||||
|
</style>
|
||||||
|
<article>
|
||||||
|
<section id="hidden">
|
||||||
|
<input id="file" type="file" accept="image/*" />
|
||||||
|
<img alt="" src="" />
|
||||||
|
</section>
|
||||||
|
<div id="view"><canvas width="200" height="200"></canvas></div>
|
||||||
|
</article>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const f = new FileReader();
|
||||||
|
const input = shadow.querySelector('#file');
|
||||||
|
const view = shadow.querySelector('#view');
|
||||||
|
const img = shadow.querySelector('img');
|
||||||
|
const canvas = shadow.querySelector('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
let width, height;
|
||||||
|
if (img.width > img.height) {
|
||||||
|
width = 200;
|
||||||
|
height = (img.height * 200) / img.width;
|
||||||
|
} else {
|
||||||
|
width = (img.width * 200) / img.height;
|
||||||
|
height = 200;
|
||||||
|
}
|
||||||
|
console.log(img.width, img.height);
|
||||||
|
console.log(width, height);
|
||||||
|
img.width = width;
|
||||||
|
img.height = height;
|
||||||
|
// ctx.drawImage(img, 0, 0);
|
||||||
|
ctx.fillStyle = '#F00';
|
||||||
|
ctx.rect(0, 0, 200, 200);
|
||||||
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
|
});
|
||||||
|
input.addEventListener('change', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
f.addEventListener('loadend', (readerEvent) => {
|
||||||
|
if (readerEvent.total !== readerEvent.loaded)
|
||||||
|
return;
|
||||||
|
img.src = readerEvent.target.result || '';
|
||||||
|
});
|
||||||
|
f.readAsDataURL(ev.target.files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.addEventListener('click', ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -1,12 +1,12 @@
|
|||||||
INSERT INTO accounts (login, pass, email)
|
INSERT INTO accounts (login, pass, email)
|
||||||
VALUES ('Foo', 'Bar', 'foo@example.com');
|
VALUES ('Foo', 'Bar', 'foo@example.com');
|
||||||
|
|
||||||
INSERT INTO local_services (name, description, owner_id)
|
INSERT INTO local_businesses (name, description, owner_id)
|
||||||
VALUES ('Cheap Tees', 'Unlimited possiblities! You can move the water masks to create your own frame. It gives you an opportunity to fit the layers to your image. Create great final effect with seconds', 1),
|
VALUES ('Cheap Tees', 'Unlimited possiblities! You can move the water masks to create your own frame. It gives you an opportunity to fit the layers to your image. Create great final effect with seconds', 1),
|
||||||
('Tema Model Agency Ltd', 'Special for website developers and app ui designers, to preview their apps in a professional way, showcasing details and focus on Responsive Design for Website and apps, Vol 09.', 1),
|
('Tema Model Agency Ltd', 'Special for website developers and app ui designers, to preview their apps in a professional way, showcasing details and focus on Responsive Design for Website and apps, Vol 09.', 1),
|
||||||
('Neet Online Test Series - Nots', 'Advanced, easy to edit mockup. It contains everything you need to create a realistic look of your project. Guarantees the a good look for bright and dark designs and perfect fit to the shape. Easy to navigate, well described layers, friendly help file.', 1);
|
('Neet Online Test Series - Nots', 'Advanced, easy to edit mockup. It contains everything you need to create a realistic look of your project. Guarantees the a good look for bright and dark designs and perfect fit to the shape. Easy to navigate, well described layers, friendly help file.', 1);
|
||||||
|
|
||||||
INSERT INTO local_service_items (name, price, local_service_id, item_order)
|
INSERT INTO local_business_items (name, price, local_business_id, item_order)
|
||||||
VALUES ('Water Frame', 23423, 1, 1),
|
VALUES ('Water Frame', 23423, 1, 1),
|
||||||
('Macbook Laptop Display 2.0', 927, 1, 2),
|
('Macbook Laptop Display 2.0', 927, 1, 2),
|
||||||
('Paper Band', 920, 1, 3),
|
('Paper Band', 920, 1, 3),
|
||||||
|
@ -11,7 +11,7 @@ CREATE TYPE "Role" AS ENUM (
|
|||||||
'Admin'
|
'Admin'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TYPE "LocalServiceState" AS ENUM (
|
CREATE TYPE "LocalBusinessState" AS ENUM (
|
||||||
'Pending',
|
'Pending',
|
||||||
'Approved',
|
'Approved',
|
||||||
'Banned',
|
'Banned',
|
||||||
@ -32,17 +32,17 @@ CREATE TABLE tokens (
|
|||||||
role "Role" not null default 'User'
|
role "Role" not null default 'User'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE local_services (
|
CREATE TABLE local_businesses (
|
||||||
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 "LocalBusinessState" not null default 'Pending'
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE local_service_items (
|
CREATE TABLE local_business_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_business_id int references local_businesses (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
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#![feature(drain_filter)]
|
#![feature(drain_filter)]
|
||||||
|
#![feature(option_get_or_insert_default)]
|
||||||
|
|
||||||
use crate::routes::render_index;
|
use crate::routes::render_index;
|
||||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||||
@ -11,12 +12,7 @@ mod utils;
|
|||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
use tracing::Level;
|
tracing_subscriber::fmt::init();
|
||||||
use tracing_subscriber::FmtSubscriber;
|
|
||||||
let subscriber = FmtSubscriber::builder()
|
|
||||||
.with_max_level(Level::DEBUG)
|
|
||||||
.finish();
|
|
||||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
|
||||||
|
|
||||||
let pool = sqlx::postgres::PgPoolOptions::new()
|
let pool = sqlx::postgres::PgPoolOptions::new()
|
||||||
.max_connections(8)
|
.max_connections(8)
|
||||||
@ -33,6 +29,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(actix_web::middleware::Compress::default())
|
.wrap(actix_web::middleware::Compress::default())
|
||||||
.wrap(actix_web::middleware::Logger::default())
|
.wrap(actix_web::middleware::Logger::default())
|
||||||
|
.wrap(tracing_actix_web::TracingLogger::default())
|
||||||
.wrap(IdentityService::new(policy))
|
.wrap(IdentityService::new(policy))
|
||||||
.app_data(Data::new(pool.clone()))
|
.app_data(Data::new(pool.clone()))
|
||||||
.configure(routes::configure)
|
.configure(routes::configure)
|
||||||
|
@ -28,7 +28,7 @@ pub enum Role {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Type)]
|
||||||
pub enum LocalServiceState {
|
pub enum LocalBusinessState {
|
||||||
Pending,
|
Pending,
|
||||||
Approved,
|
Approved,
|
||||||
Banned,
|
Banned,
|
||||||
@ -36,7 +36,7 @@ pub enum LocalServiceState {
|
|||||||
Internal,
|
Internal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalServiceState {
|
impl LocalBusinessState {
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Pending => "Pending",
|
Self::Pending => "Pending",
|
||||||
@ -68,13 +68,13 @@ pub struct LocalBusiness {
|
|||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub state: LocalServiceState,
|
pub state: LocalBusinessState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||||
pub struct LocalBusinessItem {
|
pub struct LocalBusinessItem {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub local_service_id: i32,
|
pub local_business_id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub price: i64,
|
pub price: i64,
|
||||||
pub item_order: i32,
|
pub item_order: i32,
|
||||||
|
@ -3,16 +3,17 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Page {
|
pub enum Page {
|
||||||
LocalServices,
|
LocalBusinesses,
|
||||||
News,
|
News,
|
||||||
Account,
|
Account,
|
||||||
Register,
|
Register,
|
||||||
Login,
|
Login,
|
||||||
|
BusinessItems,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
pub fn select_index(&self) -> &str {
|
pub fn select_index(&self) -> &str {
|
||||||
if matches!(self, Page::LocalServices) {
|
if matches!(self, Page::LocalBusinesses) {
|
||||||
"selected"
|
"selected"
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
@ -34,21 +35,38 @@ impl Page {
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_business_items(&self) -> &str {
|
||||||
|
if matches!(self, Page::BusinessItems) {
|
||||||
|
"selected"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct BusinessItemInput {
|
pub struct BusinessItemInput {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub price: u32,
|
pub price: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BusinessItemInput {
|
||||||
|
pub fn new<S: Into<String>>(name: S, price: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
price,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LocalService {
|
pub struct LocalService {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub state: db::LocalServiceState,
|
pub state: db::LocalBusinessState,
|
||||||
pub items: Vec<db::LocalBusinessItem>,
|
pub items: Vec<db::LocalBusinessItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +79,7 @@ impl<'v> From<(db::LocalBusiness, &'v mut Vec<db::LocalBusinessItem>)> for Local
|
|||||||
description: service.description,
|
description: service.description,
|
||||||
state: service.state,
|
state: service.state,
|
||||||
items: items
|
items: items
|
||||||
.drain_filter(|i| i.local_service_id == service.id)
|
.drain_filter(|i| i.local_business_id == service.id)
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,50 @@
|
|||||||
use actix_web::web::ServiceConfig;
|
use actix_web::web::ServiceConfig;
|
||||||
|
use actix_web::{FromRequest, HttpRequest};
|
||||||
|
use serde::Serializer;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
mod restricted;
|
||||||
mod unrestricted;
|
mod unrestricted;
|
||||||
|
|
||||||
pub use unrestricted::render_index;
|
pub use unrestricted::render_index;
|
||||||
|
|
||||||
pub fn configure(config: &mut ServiceConfig) {
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
unrestricted::configure(config);
|
unrestricted::configure(config);
|
||||||
|
restricted::configure(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Identity(actix_identity::Identity);
|
||||||
|
|
||||||
|
impl Debug for Identity {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.serialize_struct("Identity", 1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Identity {
|
||||||
|
type Target = actix_identity::Identity;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for Identity {
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Identity, actix_web::Error>>>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_request(req: &HttpRequest, p: &mut actix_http::Payload) -> Self::Future {
|
||||||
|
use futures::FutureExt;
|
||||||
|
Box::pin(
|
||||||
|
actix_identity::Identity::from_request(req, p).map(|ident| match ident {
|
||||||
|
Ok(ident) => Ok(Self(ident)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
74
src/routes/restricted.rs
Normal file
74
src/routes/restricted.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use crate::model::db;
|
||||||
|
use crate::model::view::Page;
|
||||||
|
use crate::routes::Identity;
|
||||||
|
use crate::utils;
|
||||||
|
use actix_web::web::{Data, ServiceConfig};
|
||||||
|
use actix_web::{get, HttpResponse};
|
||||||
|
use askama::*;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "business-items.html")]
|
||||||
|
struct BusinessItemsTemplate {
|
||||||
|
page: Page,
|
||||||
|
error: Option<String>,
|
||||||
|
account: Option<db::Account>,
|
||||||
|
items: Vec<db::LocalBusinessItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_unauthorized() -> HttpResponse {
|
||||||
|
HttpResponse::Unauthorized()
|
||||||
|
.append_header(("Location", "/"))
|
||||||
|
.body("")
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! authorize {
|
||||||
|
($id: expr, $pool: expr) => {{
|
||||||
|
let account = match $id.identity() {
|
||||||
|
None => return render_unauthorized(),
|
||||||
|
Some(id) => utils::user_by_id(id, &*$pool).await,
|
||||||
|
};
|
||||||
|
match account {
|
||||||
|
Some(account) => account,
|
||||||
|
_ => return render_unauthorized(),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/account/business-items")]
|
||||||
|
async fn business_items_page(db: Data<PgPool>, id: Identity) -> HttpResponse {
|
||||||
|
let pool = db.into_inner();
|
||||||
|
let account = authorize!(id, pool);
|
||||||
|
let items: Vec<db::LocalBusinessItem> = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
local_business_id,
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
item_order
|
||||||
|
FROM local_business_items
|
||||||
|
ORDER BY item_order DESC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(account.id)
|
||||||
|
.fetch_all(&*pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("{e}");
|
||||||
|
dbg!(&e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let page = BusinessItemsTemplate {
|
||||||
|
page: Page::BusinessItems,
|
||||||
|
error: None,
|
||||||
|
account: Some(account),
|
||||||
|
items,
|
||||||
|
};
|
||||||
|
HttpResponse::Ok().body(page.render().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure(config: &mut ServiceConfig) {
|
||||||
|
config.service(business_items_page);
|
||||||
|
}
|
@ -1,30 +1,32 @@
|
|||||||
use crate::model::db;
|
use crate::model::db;
|
||||||
use crate::model::db::AccountType;
|
|
||||||
use crate::model::view::{self, Page};
|
use crate::model::view::{self, Page};
|
||||||
|
use crate::routes::Identity;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
use actix_identity::Identity;
|
|
||||||
use actix_web::web::{Data, ServiceConfig};
|
use actix_web::web::{Data, ServiceConfig};
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tracing::*;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
pub struct IndexTemplate {
|
pub struct IndexTemplate {
|
||||||
services: Vec<crate::model::view::LocalService>,
|
services: Vec<view::LocalService>,
|
||||||
account: Option<db::Account>,
|
account: Option<db::Account>,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
page: Page,
|
page: Page,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
pub async fn render_index() -> HttpResponse {
|
pub async fn render_index() -> HttpResponse {
|
||||||
HttpResponse::NotFound().body(
|
HttpResponse::NotFound().body(
|
||||||
IndexTemplate {
|
IndexTemplate {
|
||||||
services: vec![],
|
services: vec![],
|
||||||
account: None,
|
account: None,
|
||||||
error: None,
|
error: None,
|
||||||
page: Page::LocalServices,
|
page: Page::LocalBusinesses,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -32,6 +34,7 @@ pub async fn render_index() -> HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
|
#[tracing::instrument]
|
||||||
pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
||||||
let pool = db.into_inner();
|
let pool = db.into_inner();
|
||||||
let record = match id.identity() {
|
let record = match id.identity() {
|
||||||
@ -43,10 +46,10 @@ pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
|||||||
let services: Vec<LocalBusiness> = 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_businesses
|
||||||
WHERE state != 'Banned'
|
WHERE state != 'Banned'
|
||||||
GROUP BY id, state
|
GROUP BY id, state
|
||||||
ORDER BY id
|
ORDER BY id DESC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.fetch_all(&*pool)
|
.fetch_all(&*pool)
|
||||||
@ -62,11 +65,12 @@ ORDER BY id
|
|||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
local_service_id,
|
local_business_id,
|
||||||
name,
|
name,
|
||||||
price,
|
price,
|
||||||
item_order
|
item_order
|
||||||
FROM local_service_items
|
FROM local_business_items
|
||||||
|
ORDER BY item_order DESC
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.fetch_all(&*pool)
|
.fetch_all(&*pool)
|
||||||
@ -92,7 +96,7 @@ FROM local_service_items
|
|||||||
services,
|
services,
|
||||||
account: record,
|
account: record,
|
||||||
error: None,
|
error: None,
|
||||||
page: Page::LocalServices,
|
page: Page::LocalBusinesses,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -108,11 +112,15 @@ struct AccountTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/account")]
|
#[get("/account")]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> HttpResponse {
|
async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> HttpResponse {
|
||||||
let pool = db.into_inner();
|
let pool = db.into_inner();
|
||||||
let record = match id.identity() {
|
let record = match id.identity() {
|
||||||
Some(id) => utils::user_by_id(id, &pool).await,
|
Some(id) => utils::user_by_id(id, &pool).await,
|
||||||
_ => None,
|
_ => {
|
||||||
|
id.forget();
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
AccountTemplate {
|
AccountTemplate {
|
||||||
@ -126,6 +134,7 @@ async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
struct RegisterForm {
|
struct RegisterForm {
|
||||||
email: String,
|
email: String,
|
||||||
login: String,
|
login: String,
|
||||||
@ -135,17 +144,67 @@ struct RegisterForm {
|
|||||||
items: Option<Vec<view::BusinessItemInput>>,
|
items: Option<Vec<view::BusinessItemInput>>,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
description: Option<String>,
|
description: Option<String>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
names: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
fn process_items(items: &mut Vec<view::BusinessItemInput>, names: HashMap<String, String>) {
|
||||||
|
let mut h = names
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(name, value)| {
|
||||||
|
let mut name = name
|
||||||
|
.strip_prefix("items")?
|
||||||
|
.split('[')
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| s.strip_suffix(']').unwrap_or(s));
|
||||||
|
let idx: u16 = name.next().and_then(|s| s.parse().ok())?;
|
||||||
|
match name.next() {
|
||||||
|
Some(s @ ("name" | "price")) => Some((idx, s.to_string(), value)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fold(
|
||||||
|
HashMap::with_capacity(60),
|
||||||
|
|mut memo, (idx, field, value)| {
|
||||||
|
let item = memo
|
||||||
|
.entry(idx)
|
||||||
|
.or_insert_with(view::BusinessItemInput::default);
|
||||||
|
match field.as_str() {
|
||||||
|
"name" => {
|
||||||
|
item.name = value;
|
||||||
|
}
|
||||||
|
"price" => {
|
||||||
|
item.price = value.parse().unwrap_or_default();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
memo
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let mut ids = { h.keys().copied().collect::<Vec<_>>() };
|
||||||
|
ids.sort();
|
||||||
|
for id in ids {
|
||||||
|
if let Some(item) = h.remove(&id) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/register")]
|
#[post("/register")]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn register(
|
async fn register(
|
||||||
form: web::Form<RegisterForm>,
|
form: web::Form<RegisterForm>,
|
||||||
db: Data<sqlx::PgPool>,
|
db: Data<sqlx::PgPool>,
|
||||||
id: Identity,
|
id: Identity,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let form = form.into_inner();
|
let mut form = form.into_inner();
|
||||||
|
{
|
||||||
|
process_items(form.items.get_or_insert_default(), form.names);
|
||||||
|
}
|
||||||
|
|
||||||
let pool = db.into_inner();
|
let pool = db.into_inner();
|
||||||
if form.account_type == AccountType::Admin {
|
if form.account_type == db::AccountType::Admin {
|
||||||
return HttpResponse::BadRequest().body("Security breach attempt detected!");
|
return HttpResponse::BadRequest().body("Security breach attempt detected!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,14 +263,17 @@ RETURNING id, login, email, pass, facebook_id, account_type
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(form.account_type, AccountType::Business) {
|
debug!("{:?}", form.account_type);
|
||||||
|
debug!("{:?}", form.items);
|
||||||
|
|
||||||
|
if matches!(form.account_type, db::AccountType::Business) {
|
||||||
let name = form.name.as_deref().unwrap_or_default();
|
let name = form.name.as_deref().unwrap_or_default();
|
||||||
let owner_id = account.id;
|
let owner_id = account.id;
|
||||||
let description = form.description.as_deref().unwrap_or_default();
|
let description = form.description.as_deref().unwrap_or_default();
|
||||||
|
|
||||||
let res: sqlx::Result<db::LocalBusiness> = sqlx::query_as(
|
let res: sqlx::Result<db::LocalBusiness> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO local_services (name, owner_id, description)
|
INSERT INTO local_businesses (name, owner_id, description)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id, owner_id, name, description, state
|
RETURNING id, owner_id, name, description, state
|
||||||
"#,
|
"#,
|
||||||
@ -239,12 +301,12 @@ RETURNING id, owner_id, name, description, state
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (idx, item) in form.items.as_deref().unwrap_or_default().iter().enumerate() {
|
for (idx, item) in form.items.unwrap_or_default().iter().enumerate() {
|
||||||
let res: sqlx::Result<db::LocalBusinessItem> = sqlx::query_as(
|
let res: sqlx::Result<db::LocalBusinessItem> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO local_service_items (local_service_id, name, price, item_order)
|
INSERT INTO local_business_items (local_business_id, name, price, item_order)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
RETURNING id, local_service_id, name, price, item_order
|
RETURNING id, local_business_id, name, price, item_order
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(business.id)
|
.bind(business.id)
|
||||||
@ -289,6 +351,7 @@ RETURNING id, local_service_id, name, price, item_order
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/logout")]
|
#[post("/logout")]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn logout(id: Identity) -> HttpResponse {
|
async fn logout(id: Identity) -> HttpResponse {
|
||||||
id.forget();
|
id.forget();
|
||||||
HttpResponse::SeeOther()
|
HttpResponse::SeeOther()
|
||||||
@ -298,7 +361,7 @@ async fn logout(id: Identity) -> HttpResponse {
|
|||||||
services: vec![],
|
services: vec![],
|
||||||
account: None,
|
account: None,
|
||||||
error: None,
|
error: None,
|
||||||
page: Page::LocalServices,
|
page: Page::LocalBusinesses,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@ -312,6 +375,7 @@ struct LoginForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login")]
|
#[post("/login")]
|
||||||
|
#[tracing::instrument]
|
||||||
async fn login(form: web::Form<LoginForm>, db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
async fn login(form: web::Form<LoginForm>, db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
|
||||||
let pool = db.into_inner();
|
let pool = db.into_inner();
|
||||||
let form = form.into_inner();
|
let form = form.into_inner();
|
||||||
@ -379,3 +443,25 @@ pub fn configure(config: &mut ServiceConfig) {
|
|||||||
.service(logout)
|
.service(logout)
|
||||||
.service(login);
|
.service(login);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::model::view;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_items() {
|
||||||
|
let mut items = Vec::with_capacity(0);
|
||||||
|
let mut names: HashMap<String, String> = HashMap::with_capacity(4);
|
||||||
|
names.insert("items[0][name]".into(), "a".into());
|
||||||
|
names.insert("items[0][price]".into(), "10".into());
|
||||||
|
names.insert("items[1][name]".into(), "b".into());
|
||||||
|
names.insert("items[1][price]".into(), "20".into());
|
||||||
|
super::process_items(&mut items, names);
|
||||||
|
let expected = vec![
|
||||||
|
view::BusinessItemInput::new("a", 10),
|
||||||
|
view::BusinessItemInput::new("b", 20),
|
||||||
|
];
|
||||||
|
assert_eq!(items, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crate::model::db;
|
|||||||
use argon2::{Algorithm, Argon2, Params, Version};
|
use argon2::{Algorithm, Argon2, Params, Version};
|
||||||
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
pub fn encrypt(pass: &str) -> password_hash::Result<String> {
|
pub fn encrypt(pass: &str) -> password_hash::Result<String> {
|
||||||
tracing::debug!("Hashing password {:?}", pass);
|
tracing::debug!("Hashing password {:?}", pass);
|
||||||
Ok(
|
Ok(
|
||||||
@ -11,6 +12,7 @@ pub fn encrypt(pass: &str) -> password_hash::Result<String> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
pub fn validate(pass: &str, pass_hash: &str) -> password_hash::Result<()> {
|
pub fn validate(pass: &str, pass_hash: &str) -> password_hash::Result<()> {
|
||||||
tracing::debug!("Validating password {:?} {:?}", pass, pass_hash);
|
tracing::debug!("Validating password {:?} {:?}", pass, pass_hash);
|
||||||
|
|
||||||
@ -20,6 +22,7 @@ pub fn validate(pass: &str, pass_hash: &str) -> password_hash::Result<()> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
pub async fn user_by_id(id: String, pool: &sqlx::PgPool) -> Option<db::Account> {
|
pub async fn user_by_id(id: String, pool: &sqlx::PgPool) -> Option<db::Account> {
|
||||||
match sqlx::query_as(
|
match sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
|
Loading…
Reference in New Issue
Block a user