Move and fix register
This commit is contained in:
parent
6d09816afc
commit
dbeb542ac3
@ -19,7 +19,7 @@
|
||||
</header>
|
||||
{% match error %}
|
||||
{% when Some with (e) %}
|
||||
<p class="error">{{e}}></p>
|
||||
<p class="error">{{e}}</p>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
<article>
|
||||
|
@ -36,6 +36,10 @@ customElements.define('business-items', class extends Component {
|
||||
<input type="hidden" name="item_order" id="item_order" />
|
||||
</form>
|
||||
</article>
|
||||
<form id="moveForm" action="/business-item/move" method="post">
|
||||
<input name="id" type="hidden" />
|
||||
<input name="item_order" type="hidden" />
|
||||
</form>
|
||||
`);
|
||||
const form = this.shadowRoot.querySelector('#create-new-item-form');
|
||||
this.addEventListener('item:submit', ev => {
|
||||
@ -56,6 +60,35 @@ customElements.define('business-items', class extends Component {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
const moveForm = this.shadowRoot.querySelector('#moveForm');
|
||||
this.addEventListener('item:up', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const item_id = ev.detail.id;
|
||||
const current = this.querySelector(`business-item[item-id="${item_id}"]`);
|
||||
if (!current) return console.warn(`business-item[item-id="${item_id}"] not found`);
|
||||
let prev = current.previousElementSibling;
|
||||
if (!prev) return console.warn(`prev of business-item[item-id="${item_id}"] not found`);
|
||||
moveForm.querySelector('[name=id]').value = item_id;
|
||||
moveForm.querySelector('[name=item_order]').value = prev.item_order;
|
||||
moveForm.submit();
|
||||
});
|
||||
this.addEventListener('item:down', ev => {
|
||||
console.warn(ev);
|
||||
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const item_id = ev.detail.id;
|
||||
const current = this.querySelector(`business-item[item-id="${item_id}"]`);
|
||||
if (!current) return;
|
||||
let next = current.nextElementSibling;
|
||||
if (!next) return;
|
||||
moveForm.querySelector('[name=id]').value = item_id;
|
||||
moveForm.querySelector('[name=item_order]').value = next.item_order;
|
||||
moveForm.submit();
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
@ -71,12 +71,6 @@ customElements.define('business-item', class extends Component {
|
||||
<form id="deleteForm" action="/business-item/delete" method="post">
|
||||
<input id="delete-id" name="id" type="hidden" />
|
||||
</form>
|
||||
<form id="moveUpForm" action="/business-item/move-up" method="post">
|
||||
<input name="id" type="hidden" />
|
||||
</form>
|
||||
<form id="moveDownForm" action="/business-item/move-down" method="post">
|
||||
<input name="id" type="hidden" />
|
||||
</form>
|
||||
</section>
|
||||
`);
|
||||
|
||||
@ -86,6 +80,7 @@ customElements.define('business-item', class extends Component {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
console.log(imageInput, ev);
|
||||
this.picture_url = imageInput.url;
|
||||
|
||||
const updateForm = this.shadowRoot.querySelector('#updateForm');
|
||||
@ -113,35 +108,27 @@ customElements.define('business-item', class extends Component {
|
||||
});
|
||||
|
||||
{
|
||||
const form = this.shadowRoot.querySelector('#moveUpForm');
|
||||
const button = this.shadowRoot.querySelector('#move-up');
|
||||
button.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
form.querySelector('[name=id]').value = this.item_id;
|
||||
this.dispatchEvent(new CustomEvent('item:up', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { id: this.item_id, item_order: this.item_order }
|
||||
}));
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
{
|
||||
const form = this.shadowRoot.querySelector('#moveDownForm');
|
||||
const button = this.shadowRoot.querySelector('#move-down');
|
||||
button.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
form.querySelector('[name=id]').value = this.item_id;
|
||||
this.dispatchEvent(new CustomEvent('item:down', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { id: this.item_id, item_order: this.item_order }
|
||||
detail: { id: this.item_id }
|
||||
}));
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -156,7 +143,7 @@ customElements.define('business-item', class extends Component {
|
||||
if (oldV === newV) return;
|
||||
switch (name) {
|
||||
case 'price':
|
||||
return this.price = newV / 100.0;
|
||||
return this.price = newV;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +161,8 @@ customElements.define('business-item', class extends Component {
|
||||
}
|
||||
|
||||
get item_order() {
|
||||
return this.getAttribute('item-order');
|
||||
const v = parseInt(this.getAttribute('item-order'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set item_order(v) {
|
||||
@ -197,7 +185,8 @@ customElements.define('business-item', class extends Component {
|
||||
|
||||
set price(v) {
|
||||
this.setAttribute('price', v);
|
||||
this.shadowRoot.querySelector('price-input').value = v;
|
||||
this.shadowRoot.querySelector('price-input').value = v / 100.0;
|
||||
this.shadowRoot.querySelector('#price').value = v;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
@ -205,8 +194,10 @@ customElements.define('business-item', class extends Component {
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
console.log('picture_url', v);
|
||||
if (!v.startsWith("/")) v = "";
|
||||
this.setAttribute('picture-url', v);
|
||||
this.shadowRoot.querySelector('image-input').url = v;
|
||||
this.shadowRoot.querySelector('#picture_url').value = v;
|
||||
}
|
||||
});
|
||||
|
@ -26,17 +26,17 @@ customElements.define('register-item-form-row', class extends PseudoForm {
|
||||
<image-input></image-input>
|
||||
<div id="name">
|
||||
<label>Nazwa</label>
|
||||
<input class="item-name" name="items[none][name]" type="text" required />
|
||||
<input id="name" class="item-name" name="items[none][name]" type="text" required />
|
||||
</div>
|
||||
<div id="price">
|
||||
<label>Cena</label>
|
||||
<price-input class="item-price" name="items[none][price]" required >
|
||||
<price-input id="price" class="item-price" name="items[none][price]" required >
|
||||
</price-input>
|
||||
</div>
|
||||
<input id="submit-button" type="submit" value="Zapisz" />
|
||||
<input id="remove-button" type="submit" value="Usuń" />
|
||||
|
||||
<input type="hidden" name="picture_url" id="picture_url" />
|
||||
<input type="hidden" name="items[none][picture_url]" id="picture_url" />
|
||||
<slot name="tail"></slot>
|
||||
</form>
|
||||
</section>
|
||||
@ -78,7 +78,7 @@ customElements.define('register-item-form-row', class extends PseudoForm {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.updateNames(this.idx);
|
||||
this.#updateNames(this.idx);
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
@ -93,17 +93,22 @@ customElements.define('register-item-form-row', class extends PseudoForm {
|
||||
}
|
||||
|
||||
get inputs() {
|
||||
return this.#inputs.map(extract);
|
||||
}
|
||||
|
||||
get #inputs() {
|
||||
return [
|
||||
extract(this.shadowRoot.querySelector('.item-name')),
|
||||
extract(this.shadowRoot.querySelector('.item-price')),
|
||||
this.shadowRoot.querySelector('.item-name'),
|
||||
this.shadowRoot.querySelector('.item-price'),
|
||||
this.shadowRoot.querySelector('#picture_url'),
|
||||
];
|
||||
}
|
||||
|
||||
updateNames() {
|
||||
#updateNames() {
|
||||
const idx = this.idx;
|
||||
for (const el of this.shadowRoot.querySelectorAll('.field')) {
|
||||
const id = el.id;
|
||||
el.querySelector('input, price-input').setAttribute('name', `items[${ idx }][${ id }]`);
|
||||
for (const el of this.#inputs) {
|
||||
console.log(el);
|
||||
el.setAttribute('name', `items[${ idx }][${ el.id }]`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,6 +119,7 @@ customElements.define('register-item-form-row', class extends PseudoForm {
|
||||
|
||||
set idx(idx) {
|
||||
this.setAttribute('idx', idx);
|
||||
this.#updateNames(idx);
|
||||
}
|
||||
|
||||
get name() {
|
||||
@ -125,6 +131,15 @@ customElements.define('register-item-form-row', class extends PseudoForm {
|
||||
this.shadowRoot.querySelector('.item-name').value = v;
|
||||
}
|
||||
|
||||
get price() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
|
||||
set price(v) {
|
||||
this.setAttribute('price', v);
|
||||
this.shadowRoot.querySelector('.item-price').value = v;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
return this.getAttribute('picture-url');
|
||||
}
|
||||
|
@ -67,11 +67,13 @@ customElements.define('register-submit-form', class extends PseudoForm {
|
||||
for (const row of items) {
|
||||
const el = host.appendChild(document.createElement('div'));
|
||||
el.className = 'item-view';
|
||||
const [name, price] = row;
|
||||
const [name, price, img] = row;
|
||||
|
||||
el.innerHTML = `
|
||||
<img src="${img.value}" />
|
||||
<input type="text" name="${ name.name }" value="${ name.value }" readonly />
|
||||
<input type="hidden" name="${ price.name }" value="${ price.value }" readonly />
|
||||
<input type="hidden" name="${ img.name }" value="${ img.value }" readonly />
|
||||
<price-view value="${ price.value }"></price-view>
|
||||
`;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
|
||||
pub struct FoundClaims {
|
||||
pub iss: String,
|
||||
pub sub: String,
|
||||
|
@ -9,6 +9,7 @@ use crate::routes::render_index;
|
||||
|
||||
mod auth;
|
||||
mod model;
|
||||
pub mod queries;
|
||||
mod routes;
|
||||
mod utils;
|
||||
|
||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Type};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, PartialOrd, PartialEq, Copy, Clone, Serialize, Deserialize, Type)]
|
||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Type)]
|
||||
pub enum AccountType {
|
||||
User,
|
||||
Business,
|
||||
@ -81,3 +81,22 @@ pub struct LocalBusinessItem {
|
||||
pub item_order: i32,
|
||||
pub picture_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CreateLocalBusinessItemInput {
|
||||
pub local_business_id: i32,
|
||||
pub name: String,
|
||||
pub price: i64,
|
||||
pub item_order: i32,
|
||||
pub picture_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateLocalBusinessItemInput {
|
||||
pub id: i32,
|
||||
pub local_business_id: i32,
|
||||
pub name: String,
|
||||
pub price: i64,
|
||||
pub item_order: i32,
|
||||
pub picture_url: String,
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ impl Page {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BusinessItemInput {
|
||||
pub name: String,
|
||||
pub price: u32,
|
||||
@ -90,7 +90,7 @@ impl<'v> From<(db::LocalBusiness, &'v mut Vec<db::LocalBusinessItem>)> for Local
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct CreateBusinessItemInput {
|
||||
pub name: String,
|
||||
pub price: i32,
|
||||
pub price: i64,
|
||||
pub picture_url: String,
|
||||
pub item_order: i32,
|
||||
}
|
||||
@ -99,12 +99,18 @@ pub struct CreateBusinessItemInput {
|
||||
pub struct UpdateBusinessItemInput {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub price: i32,
|
||||
pub price: i64,
|
||||
pub picture_url: String,
|
||||
pub item_order: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct DeleteBusinessItemInput {
|
||||
pub struct ModifyBusinessItemInput {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct MoveBusinessItemInput {
|
||||
pub id: i32,
|
||||
pub item_order: i32,
|
||||
}
|
||||
|
421
src/queries/mod.rs
Normal file
421
src/queries/mod.rs
Normal file
@ -0,0 +1,421 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use tracing::error;
|
||||
|
||||
use crate::model::db;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
SetOrder {
|
||||
item_id: i32,
|
||||
idx: i32,
|
||||
},
|
||||
DeleteItem {
|
||||
item_id: i32,
|
||||
},
|
||||
CreateItem {
|
||||
input: db::CreateLocalBusinessItemInput,
|
||||
},
|
||||
UpdateItem {
|
||||
input: db::UpdateLocalBusinessItemInput,
|
||||
},
|
||||
UpdateItemOrder {
|
||||
id: i32,
|
||||
item_order: i32,
|
||||
},
|
||||
AllItems,
|
||||
OwnedBusiness {
|
||||
account_id: i32,
|
||||
},
|
||||
AccountByEmail {
|
||||
email: String,
|
||||
},
|
||||
Item {
|
||||
item_id: i32,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub type T<'l> = sqlx::Transaction<'l, sqlx::Postgres>;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn create_item(
|
||||
t: &mut T<'_>,
|
||||
input: db::CreateLocalBusinessItemInput,
|
||||
) -> Result<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO local_business_items (local_business_id, name, price, picture_url, item_order)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(input.local_business_id)
|
||||
.bind(&input.name)
|
||||
.bind(input.price)
|
||||
.bind(if input.picture_url.is_empty() {
|
||||
format!("--{}", uuid::Uuid::new_v4())
|
||||
} else {
|
||||
input.picture_url.clone()
|
||||
})
|
||||
.bind(input.item_order)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::CreateItem { input }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn set_item_order(
|
||||
t: &mut T<'_>,
|
||||
item_id: i32,
|
||||
idx: i32,
|
||||
) -> Result<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE local_business_items
|
||||
SET
|
||||
item_order = $2
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(item_id)
|
||||
.bind(idx as i32 + 1)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::SetOrder { item_id, idx }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn delete_item(t: &mut T<'_>, item_id: i32) -> Result<Option<db::LocalBusinessItem>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
DELETE FROM local_business_items
|
||||
WHERE id = $1
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(item_id)
|
||||
.fetch_optional(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::DeleteItem { item_id }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn account_by_id(t: &mut T<'_>, id: String) -> Option<db::Account> {
|
||||
match sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, login, email, pass, facebook_id, account_type
|
||||
FROM accounts
|
||||
WHERE id = $1 :: INT
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(t)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn account_business_by_id(t: &mut T<'_>, account_id: i32) -> Result<db::LocalBusiness> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, owner_id, name, description, state
|
||||
FROM local_businesses
|
||||
WHERE state != 'Banned' AND owner_id = $1
|
||||
GROUP BY id, state
|
||||
ORDER BY id DESC
|
||||
"#,
|
||||
)
|
||||
.bind(account_id)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::OwnedBusiness { account_id }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn account_items(t: &mut T<'_>, account_id: i32) -> Vec<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
local_business_items.id,
|
||||
local_business_items.local_business_id,
|
||||
local_business_items.name,
|
||||
local_business_items.price,
|
||||
local_business_items.item_order,
|
||||
local_business_items.picture_url
|
||||
FROM local_business_items
|
||||
INNER JOIN local_businesses
|
||||
ON local_businesses.id = local_business_items.local_business_id
|
||||
WHERE local_businesses.owner_id = $1
|
||||
ORDER BY item_order ASC
|
||||
"#,
|
||||
)
|
||||
.bind(account_id)
|
||||
.fetch_all(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::AllItems
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn item_by_id(
|
||||
t: &mut T<'_>,
|
||||
account_id: i32,
|
||||
item_id: i32,
|
||||
) -> Result<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
local_business_items.id,
|
||||
local_business_items.local_business_id,
|
||||
local_business_items.name,
|
||||
local_business_items.price,
|
||||
local_business_items.item_order,
|
||||
local_business_items.picture_url
|
||||
FROM local_business_items
|
||||
INNER JOIN local_businesses
|
||||
ON local_businesses.id = local_business_items.local_business_id
|
||||
WHERE local_business_items.id = $1 AND owner_id = $2
|
||||
ORDER BY item_order ASC
|
||||
"#,
|
||||
)
|
||||
.bind(item_id)
|
||||
.bind(account_id)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::Item { item_id }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn move_item(
|
||||
t: &mut T<'_>,
|
||||
account_id: i32,
|
||||
item_id: i32,
|
||||
item_order: i32,
|
||||
) -> Result<db::LocalBusinessItem> {
|
||||
let mut current = item_by_id(t, account_id, item_id).await?;
|
||||
|
||||
let all: Vec<db::LocalBusinessItem> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
local_business_items.id,
|
||||
local_business_items.local_business_id,
|
||||
local_business_items.name,
|
||||
local_business_items.price,
|
||||
local_business_items.item_order,
|
||||
local_business_items.picture_url
|
||||
FROM local_business_items
|
||||
INNER JOIN local_businesses
|
||||
ON local_businesses.id = local_business_items.local_business_id
|
||||
WHERE local_businesses.owner_id = $1
|
||||
ORDER BY item_order ASC
|
||||
"#,
|
||||
)
|
||||
.bind(account_id)
|
||||
.fetch_all(&mut *t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::Item { item_id }
|
||||
})?;
|
||||
|
||||
let idx = all
|
||||
.iter()
|
||||
.position(|p| p.id == item_id)
|
||||
.ok_or(Error::Item { item_id })?;
|
||||
|
||||
dbg!(idx);
|
||||
|
||||
match item_order.cmp(¤t.item_order) {
|
||||
Ordering::Less => {
|
||||
if let Some(prev) = idx.checked_sub(1).and_then(|prev_idx| {
|
||||
dbg!(prev_idx);
|
||||
all.get(prev_idx)
|
||||
}) {
|
||||
dbg!(
|
||||
"Less and found",
|
||||
current.id,
|
||||
current.item_order,
|
||||
prev.id,
|
||||
prev.item_order,
|
||||
);
|
||||
dbg!(update_item_order(&mut *t, current.id, prev.item_order).await?);
|
||||
dbg!(update_item_order(&mut *t, prev.id, current.item_order).await?);
|
||||
} else {
|
||||
dbg!("Less and not found, skipping...");
|
||||
}
|
||||
}
|
||||
Ordering::Equal => {
|
||||
dbg!("Equal, skipping...");
|
||||
}
|
||||
Ordering::Greater => {
|
||||
if let Some(next) = idx.checked_add(1).and_then(|next_idx| {
|
||||
dbg!(next_idx);
|
||||
all.get(next_idx)
|
||||
}) {
|
||||
dbg!(
|
||||
"Greater and found",
|
||||
current.id,
|
||||
current.item_order,
|
||||
next.id,
|
||||
next.item_order,
|
||||
);
|
||||
dbg!(update_item_order(&mut *t, current.id, next.item_order).await?);
|
||||
dbg!(update_item_order(&mut *t, next.id, current.item_order).await?);
|
||||
} else {
|
||||
dbg!("Greater and not found, skipping...");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
current.item_order = item_order;
|
||||
Ok(current)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn update_item_order(
|
||||
t: &mut T<'_>,
|
||||
id: i32,
|
||||
item_order: i32,
|
||||
) -> Result<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE local_business_items
|
||||
SET
|
||||
item_order = $2
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.bind(item_order)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::UpdateItemOrder { id, item_order }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn update_item(
|
||||
t: &mut T<'_>,
|
||||
input: db::UpdateLocalBusinessItemInput,
|
||||
) -> Result<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE local_business_items
|
||||
SET
|
||||
name = $3,
|
||||
price = $4,
|
||||
picture_url = $5,
|
||||
item_order = $6
|
||||
WHERE
|
||||
local_business_id = $1 AND
|
||||
id = $2
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(input.local_business_id)
|
||||
.bind(input.id)
|
||||
.bind(&input.name)
|
||||
.bind(input.price)
|
||||
.bind(if input.picture_url.is_empty() {
|
||||
format!("--{}", uuid::Uuid::new_v4())
|
||||
} else {
|
||||
input.picture_url.clone()
|
||||
})
|
||||
.bind(input.item_order)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::UpdateItem { input }
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn account_by_email(t: &mut T<'_>, email: String) -> Result<db::Account> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, login, email, pass, facebook_id, account_type
|
||||
FROM accounts
|
||||
WHERE email = $1
|
||||
"#,
|
||||
)
|
||||
.bind(&email)
|
||||
.fetch_one(t)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::AccountByEmail { email }
|
||||
})
|
||||
}
|
@ -12,6 +12,30 @@ use serde::Serializer;
|
||||
mod restricted;
|
||||
mod unrestricted;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ok_or_internal {
|
||||
($try: expr) => {
|
||||
match $try {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
dbg!(e);
|
||||
return Err($crate::routes::Error::DatabaseQuery);
|
||||
}
|
||||
}
|
||||
};
|
||||
(json $try: expr) => {
|
||||
match $try {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
dbg!(e);
|
||||
return Err($crate::routes::Error::DatabaseQuery.to_json());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub use unrestricted::render_index;
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
@ -60,6 +84,7 @@ pub enum Error {
|
||||
UploadFailed,
|
||||
OwnedBusinessNotFound { account_id: i32 },
|
||||
OwnedBusinessItemNotFound { account_id: i32, business_id: i32 },
|
||||
DatabaseQuery,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@ -90,6 +115,9 @@ impl Display for Error {
|
||||
business_id, account_id
|
||||
)),
|
||||
Error::UploadFailed => f.write_str("Nie można zapisać pliku"),
|
||||
Error::DatabaseQuery => {
|
||||
f.write_str("Problem z zapisaniem zmian. Proszę spróbować później")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -101,6 +129,7 @@ impl ResponseError for Error {
|
||||
Error::OwnedBusinessNotFound { .. } => StatusCode::BAD_REQUEST,
|
||||
Error::OwnedBusinessItemNotFound { .. } => StatusCode::BAD_REQUEST,
|
||||
Error::UploadFailed => StatusCode::BAD_REQUEST,
|
||||
Error::DatabaseQuery => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,6 +179,7 @@ impl ResponseError for JsonError {
|
||||
Error::OwnedBusinessNotFound { .. } => StatusCode::BAD_REQUEST,
|
||||
Error::OwnedBusinessItemNotFound { .. } => StatusCode::BAD_REQUEST,
|
||||
Error::UploadFailed => StatusCode::BAD_REQUEST,
|
||||
Error::DatabaseQuery => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{get, HttpResponse};
|
||||
use askama::*;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::model::{db, view};
|
||||
use crate::queries;
|
||||
use crate::routes::{Identity, Result};
|
||||
use crate::utils;
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
#[template(path = "business-items.html")]
|
||||
@ -19,10 +17,10 @@ struct BusinessItemsTemplate {
|
||||
}
|
||||
|
||||
macro_rules! authorize {
|
||||
($id: expr, $pool: expr) => {{
|
||||
($t: expr, $id: expr) => {{
|
||||
let account = match $id.identity() {
|
||||
None => return Err(crate::routes::Error::Unauthorized),
|
||||
Some(id) => crate::utils::user_by_id(id, &*$pool).await,
|
||||
Some(id) => crate::queries::account_by_id($t, id).await,
|
||||
};
|
||||
match account {
|
||||
Some(account) => account,
|
||||
@ -34,13 +32,27 @@ macro_rules! authorize {
|
||||
#[get("/account/business-items")]
|
||||
#[tracing::instrument]
|
||||
async fn business_items_page(db: Data<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||
handle_business_items_page(db.into_inner(), id).await
|
||||
let pool = db.into_inner();
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
match handle_business_items_page(&mut t, id).await {
|
||||
Ok(res) => {
|
||||
t.commit().await.ok();
|
||||
Ok(res)
|
||||
}
|
||||
Err(res) => {
|
||||
t.rollback().await.ok();
|
||||
Err(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_business_items_page(pool: Arc<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||
let account = authorize!(id, pool);
|
||||
async fn handle_business_items_page(
|
||||
t: &mut sqlx::Transaction<'_, sqlx::postgres::Postgres>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse> {
|
||||
let account = authorize!(t, id);
|
||||
|
||||
let items: Vec<db::LocalBusinessItem> = utils::account_items(account.id, pool.clone()).await;
|
||||
let items: Vec<db::LocalBusinessItem> = queries::account_items(t, account.id).await;
|
||||
let page = BusinessItemsTemplate {
|
||||
page: view::Page::BusinessItems,
|
||||
error: None,
|
||||
@ -59,9 +71,8 @@ mod business_item {
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::model::{db, view};
|
||||
use crate::routes::restricted::handle_business_items_page;
|
||||
use crate::routes::{Error, Identity, Result};
|
||||
use crate::utils;
|
||||
use crate::{queries, routes};
|
||||
|
||||
#[post("/update")]
|
||||
#[tracing::instrument]
|
||||
@ -73,61 +84,57 @@ mod business_item {
|
||||
let form = form.into_inner();
|
||||
dbg!(&form);
|
||||
let pool = db.into_inner();
|
||||
let account = authorize!(id, pool);
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let account = authorize!(&mut t, id);
|
||||
|
||||
{
|
||||
let business: db::LocalBusiness =
|
||||
utils::account_business_by_id(account.id, pool.clone()).await?;
|
||||
match queries::account_business_by_id(&mut t, account.id).await {
|
||||
Ok(business) => business,
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Err(routes::Error::OwnedBusinessNotFound {
|
||||
account_id: account.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let item: db::LocalBusinessItem = sqlx::query_as(
|
||||
r#"
|
||||
UPDATE local_business_items
|
||||
SET
|
||||
name = $3,
|
||||
price = $4,
|
||||
picture_url = $5,
|
||||
item_order = $6
|
||||
WHERE
|
||||
local_business_id = $1 AND
|
||||
id = $2
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
let item: db::LocalBusinessItem = match queries::update_item(
|
||||
&mut t,
|
||||
db::UpdateLocalBusinessItemInput {
|
||||
id: form.id,
|
||||
local_business_id: business.id,
|
||||
name: form.name,
|
||||
price: form.price,
|
||||
item_order: form.item_order,
|
||||
picture_url: form.picture_url,
|
||||
},
|
||||
)
|
||||
.bind(business.id)
|
||||
.bind(form.id)
|
||||
.bind(form.name)
|
||||
.bind(form.price)
|
||||
.bind(if form.picture_url.is_empty() {
|
||||
format!("--{}", uuid::Uuid::new_v4())
|
||||
} else {
|
||||
form.picture_url
|
||||
})
|
||||
.bind(form.item_order)
|
||||
.fetch_one(&*pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(&e);
|
||||
Error::OwnedBusinessItemNotFound {
|
||||
account_id: account.id,
|
||||
business_id: business.id,
|
||||
{
|
||||
Ok(item) => item,
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Err(Error::OwnedBusinessItemNotFound {
|
||||
account_id: account.id,
|
||||
business_id: business.id,
|
||||
});
|
||||
}
|
||||
})?;
|
||||
};
|
||||
info!("{:?}", item);
|
||||
}
|
||||
handle_business_items_page(pool, id).await?;
|
||||
t.commit().await.ok();
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location", "/account/business-items"))
|
||||
.finish())
|
||||
}
|
||||
|
||||
#[post("/new")]
|
||||
#[tracing::instrument]
|
||||
async fn new_business_item(
|
||||
form: Form<view::CreateBusinessItemInput>,
|
||||
db: Data<PgPool>,
|
||||
@ -136,116 +143,109 @@ RETURNING
|
||||
let form = form.into_inner();
|
||||
dbg!(&form);
|
||||
let pool = db.into_inner();
|
||||
let account = authorize!(id, pool);
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let account = authorize!(&mut t, id);
|
||||
|
||||
{
|
||||
let business: db::LocalBusiness =
|
||||
utils::account_business_by_id(account.id, pool.clone()).await?;
|
||||
|
||||
let item: db::LocalBusinessItem = sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO local_business_items (local_business_id, name, price, picture_url, item_order)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(business.id)
|
||||
.bind(form.name)
|
||||
.bind(form.price)
|
||||
.bind(if form.picture_url.is_empty() {
|
||||
format!("--{}", uuid::Uuid::new_v4())
|
||||
} else {
|
||||
form.picture_url
|
||||
})
|
||||
.bind(form.item_order)
|
||||
.fetch_one(&*pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(&e);
|
||||
Error::OwnedBusinessItemNotFound {
|
||||
account_id: account.id,
|
||||
business_id: business.id,
|
||||
let business: db::LocalBusiness =
|
||||
match queries::account_business_by_id(&mut t, account.id).await {
|
||||
Ok(business) => business,
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Err(routes::Error::OwnedBusinessNotFound {
|
||||
account_id: account.id,
|
||||
});
|
||||
}
|
||||
})?;
|
||||
info!("{:?}", item);
|
||||
}
|
||||
};
|
||||
|
||||
handle_business_items_page(pool, id).await?;
|
||||
if let Err(e) = queries::create_item(
|
||||
&mut t,
|
||||
db::CreateLocalBusinessItemInput {
|
||||
local_business_id: business.id,
|
||||
name: form.name,
|
||||
price: form.price,
|
||||
item_order: form.item_order,
|
||||
picture_url: form.picture_url,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Err(Error::OwnedBusinessItemNotFound {
|
||||
account_id: account.id,
|
||||
business_id: business.id,
|
||||
});
|
||||
};
|
||||
|
||||
t.commit().await.ok();
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location", "/account/business-items"))
|
||||
.finish())
|
||||
}
|
||||
|
||||
#[post("/delete")]
|
||||
#[tracing::instrument]
|
||||
async fn delete_business_item(
|
||||
form: Form<view::DeleteBusinessItemInput>,
|
||||
form: Form<view::ModifyBusinessItemInput>,
|
||||
db: Data<PgPool>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse> {
|
||||
let form = form.into_inner();
|
||||
dbg!(&form);
|
||||
let pool = db.into_inner();
|
||||
let account = authorize!(id, pool);
|
||||
dbg!(&account);
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let account = authorize!(&mut t, id);
|
||||
|
||||
if let Err(e) = sqlx::query_as::<_, db::LocalBusinessItem>(
|
||||
r#"
|
||||
DELETE FROM local_business_items
|
||||
WHERE id = $1
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(form.id)
|
||||
.fetch_optional(&*pool)
|
||||
.await
|
||||
{
|
||||
tracing::error!("{e}");
|
||||
if let Err(e) = queries::delete_item(&mut t, form.id).await {
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
}
|
||||
|
||||
let items = utils::account_items(account.id, pool.clone()).await;
|
||||
let items = queries::account_items(&mut t, account.id).await;
|
||||
for (idx, item) in items.into_iter().enumerate() {
|
||||
if let Err(e) = sqlx::query_as::<_, db::LocalBusinessItem>(
|
||||
r#"
|
||||
UPDATE local_business_items
|
||||
SET
|
||||
item_order = $2
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING
|
||||
id,
|
||||
local_business_id,
|
||||
name,
|
||||
price,
|
||||
item_order,
|
||||
picture_url
|
||||
"#,
|
||||
)
|
||||
.bind(item.id)
|
||||
.bind(idx as i32 + 1)
|
||||
.fetch_optional(&*pool)
|
||||
.await
|
||||
{
|
||||
error!("{e}");
|
||||
if let Err(e) = queries::set_item_order(&mut t, item.id, idx as i32 + 1).await {
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
}
|
||||
}
|
||||
|
||||
t.commit().await.ok();
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location", "/account/business-items"))
|
||||
.finish())
|
||||
}
|
||||
|
||||
#[post("/move")]
|
||||
#[tracing::instrument]
|
||||
async fn move_item(
|
||||
form: Form<view::MoveBusinessItemInput>,
|
||||
db: Data<PgPool>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse> {
|
||||
let form = form.into_inner();
|
||||
dbg!(&form);
|
||||
let pool = db.into_inner();
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let account = authorize!(&mut t, id);
|
||||
|
||||
match queries::move_item(&mut t, account.id, form.id, form.item_order).await {
|
||||
Ok(item) => item,
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
}
|
||||
};
|
||||
t.commit().await.ok();
|
||||
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location", "/account/business-items"))
|
||||
.finish())
|
||||
@ -256,7 +256,8 @@ RETURNING
|
||||
actix_web::web::scope("/business-item")
|
||||
.service(new_business_item)
|
||||
.service(update_business_item)
|
||||
.service(delete_business_item),
|
||||
.service(delete_business_item)
|
||||
.service(move_item),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,13 @@ use actix_web::*;
|
||||
use askama::Template;
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use tracing::*;
|
||||
|
||||
use crate::model::db;
|
||||
use crate::model::view::{self, Page};
|
||||
use crate::routes::{Error, Identity, JsonResult, Result};
|
||||
use crate::utils;
|
||||
use crate::{queries, routes, utils};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
@ -43,8 +44,9 @@ pub async fn render_index() -> HttpResponse {
|
||||
#[tracing::instrument]
|
||||
pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||
let pool = db.into_inner();
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let record = match id.identity() {
|
||||
Some(id) => utils::user_by_id(id, &pool).await,
|
||||
Some(id) => queries::account_by_id(&mut t, id).await,
|
||||
_ => None,
|
||||
};
|
||||
let (services, mut items) = {
|
||||
@ -61,7 +63,7 @@ ORDER BY id DESC
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("{e}");
|
||||
error!("{e}");
|
||||
dbg!(&e);
|
||||
e
|
||||
})
|
||||
@ -83,7 +85,7 @@ ORDER BY item_order ASC
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("{e}");
|
||||
error!("{e}");
|
||||
dbg!(&e);
|
||||
e
|
||||
})
|
||||
@ -107,6 +109,9 @@ ORDER BY item_order ASC
|
||||
}
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
t.commit().await.ok();
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.append_header(("Content-Type", "text/html"))
|
||||
.body(body))
|
||||
@ -124,13 +129,15 @@ struct AccountTemplate {
|
||||
#[tracing::instrument]
|
||||
async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> Result<HttpResponse> {
|
||||
let pool = db.into_inner();
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let record = match id.identity() {
|
||||
Some(id) => utils::user_by_id(id, &pool).await,
|
||||
Some(id) => queries::account_by_id(&mut t, id).await,
|
||||
_ => {
|
||||
id.forget();
|
||||
None
|
||||
}
|
||||
};
|
||||
t.commit().await.ok();
|
||||
Ok(HttpResponse::Ok().body(
|
||||
AccountTemplate {
|
||||
account: record,
|
||||
@ -205,15 +212,10 @@ fn process_items(items: &mut Vec<view::BusinessItemInput>, names: HashMap<String
|
||||
|
||||
#[post("/register")]
|
||||
#[tracing::instrument]
|
||||
async fn register(
|
||||
form: web::Form<RegisterForm>,
|
||||
db: Data<sqlx::PgPool>,
|
||||
id: Identity,
|
||||
) -> HttpResponse {
|
||||
async fn register(form: web::Form<RegisterForm>, db: Data<PgPool>, id: Identity) -> HttpResponse {
|
||||
let mut form = form.into_inner();
|
||||
{
|
||||
process_items(form.items.get_or_insert_default(), form.names);
|
||||
}
|
||||
dbg!(&form);
|
||||
process_items(form.items.get_or_insert_default(), form.names);
|
||||
|
||||
let pool = db.into_inner();
|
||||
if form.account_type == db::AccountType::Admin {
|
||||
@ -325,7 +327,51 @@ RETURNING id, local_business_id, name, price, item_order, picture_url
|
||||
.bind(if item.picture_url.is_empty() {
|
||||
format!("--{}", uuid::Uuid::new_v4())
|
||||
} else {
|
||||
item.picture_url
|
||||
let name = item.picture_url.split('/').last().unwrap_or_default();
|
||||
let dir = item_picture_write_dir(format!("{}", account.id));
|
||||
if let Err(e) = std::fs::create_dir_all(&dir) {
|
||||
error!("{e} {:?}", dir);
|
||||
dbg!(e);
|
||||
t.rollback().await.unwrap();
|
||||
return HttpResponse::BadRequest()
|
||||
.append_header(("Content-Type", "text/html"))
|
||||
.body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some(
|
||||
"Problem z utworzeniem konta. Nie można zapisać zdjęcia."
|
||||
.into(),
|
||||
),
|
||||
page: Page::Register,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
let path = dir.join(name);
|
||||
if let Err(e) = std::fs::rename(format!(".{}", item.picture_url), &path) {
|
||||
error!("{e} {:?}", item.picture_url);
|
||||
dbg!(e);
|
||||
t.rollback().await.unwrap();
|
||||
return HttpResponse::BadRequest()
|
||||
.append_header(("Content-Type", "text/html"))
|
||||
.body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some(
|
||||
"Problem z utworzeniem konta. Nie można zapisać zdjęcia."
|
||||
.into(),
|
||||
),
|
||||
page: Page::Register,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
let path = path.to_str().map(String::from).unwrap_or_default();
|
||||
path.strip_prefix('.')
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| path)
|
||||
})
|
||||
.fetch_one(&mut t)
|
||||
.await;
|
||||
@ -392,24 +438,17 @@ struct LoginForm {
|
||||
|
||||
#[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<PgPool>, id: Identity) -> Result<HttpResponse> {
|
||||
let pool = db.into_inner();
|
||||
let mut t = crate::ok_or_internal!(pool.begin().await);
|
||||
let form = form.into_inner();
|
||||
let record: db::Account = match sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, login, email, pass, facebook_id, account_type
|
||||
FROM accounts
|
||||
WHERE email = $1
|
||||
"#,
|
||||
)
|
||||
.bind(form.email)
|
||||
.fetch_one(&*pool)
|
||||
.await
|
||||
{
|
||||
let record: db::Account = match queries::account_by_email(&mut t, form.email).await {
|
||||
Ok(record) => record,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
return HttpResponse::Ok().body(
|
||||
tracing::error!("{e:?}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Ok(HttpResponse::Ok().body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some("Nie znaleziono konta".into()),
|
||||
@ -417,11 +456,14 @@ WHERE email = $1
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
));
|
||||
}
|
||||
};
|
||||
if utils::validate(&form.password, &record.pass).is_err() {
|
||||
return HttpResponse::BadRequest().body(
|
||||
if let Err(e) = utils::validate(&form.password, &record.pass) {
|
||||
tracing::error!("{e}");
|
||||
dbg!(e);
|
||||
t.rollback().await.ok();
|
||||
return Ok(HttpResponse::BadRequest().body(
|
||||
AccountTemplate {
|
||||
account: None,
|
||||
error: Some("Hasło i/lub adres e-mail są nieprawidłowe".into()),
|
||||
@ -429,10 +471,12 @@ WHERE email = $1
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
));
|
||||
}
|
||||
id.remember(format!("{}", record.id));
|
||||
HttpResponse::Ok()
|
||||
t.commit().await.ok();
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.append_header(("Content-Type", "text/html"))
|
||||
.body(
|
||||
AccountTemplate {
|
||||
@ -442,7 +486,7 @@ WHERE email = $1
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@ -450,13 +494,25 @@ struct UploadResponse {
|
||||
path: String,
|
||||
}
|
||||
|
||||
fn item_picture_write_dir(account_id: String) -> PathBuf {
|
||||
PathBuf::new().join("./uploads").join(account_id)
|
||||
}
|
||||
|
||||
#[post("/upload")]
|
||||
async fn upload(mut payload: actix_multipart::Multipart, id: Identity) -> JsonResult<HttpResponse> {
|
||||
let path = PathBuf::new().join(
|
||||
id.identity()
|
||||
.map(|id| format!("./uploads/{id}"))
|
||||
.unwrap_or_else(|| "./uploads/tmp".into()),
|
||||
);
|
||||
async fn upload(
|
||||
mut payload: actix_multipart::Multipart,
|
||||
db: Data<PgPool>,
|
||||
id: Identity,
|
||||
) -> JsonResult<HttpResponse> {
|
||||
let pool = db.into_inner();
|
||||
let mut t = crate::ok_or_internal!(json pool.begin().await);
|
||||
let id = match id.identity() {
|
||||
Some(id) => queries::account_by_id(&mut t, id).await.map(|a| a.id),
|
||||
_ => None,
|
||||
};
|
||||
t.commit().await.ok();
|
||||
|
||||
let path = item_picture_write_dir(id.map(|id| format!("{id}")).unwrap_or_else(|| "tmp".into()));
|
||||
std::fs::create_dir_all(&path).map_err(|e| {
|
||||
error!("Cannot create upload directory {:?}", path);
|
||||
dbg!(e);
|
||||
@ -465,7 +521,7 @@ async fn upload(mut payload: actix_multipart::Multipart, id: Identity) -> JsonRe
|
||||
|
||||
if let Some(item) = payload.next().await {
|
||||
let mut field = item.map_err(|e| {
|
||||
warn!("Malformed upload file",);
|
||||
warn!("Malformed upload file");
|
||||
dbg!(e);
|
||||
Error::UploadFailed.to_json()
|
||||
})?;
|
||||
|
80
src/utils.rs
80
src/utils.rs
@ -1,11 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use argon2::{Algorithm, Argon2, Params, Version};
|
||||
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
||||
use tracing::error;
|
||||
|
||||
use crate::model::db;
|
||||
use crate::routes::Error;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn encrypt(pass: &str) -> password_hash::Result<String> {
|
||||
@ -27,80 +21,6 @@ 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> {
|
||||
match sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, login, email, pass, facebook_id, account_type
|
||||
FROM accounts
|
||||
WHERE id = $1 :: INT
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
tracing::error!("{e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn account_business_by_id(
|
||||
account_id: i32,
|
||||
pool: Arc<sqlx::PgPool>,
|
||||
) -> crate::routes::Result<db::LocalBusiness> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, owner_id, name, description, state
|
||||
FROM local_businesses
|
||||
WHERE state != 'Banned' AND owner_id = $1
|
||||
GROUP BY id, state
|
||||
ORDER BY id DESC
|
||||
"#,
|
||||
)
|
||||
.bind(account_id)
|
||||
.fetch_one(&*pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{e}");
|
||||
dbg!(e);
|
||||
Error::OwnedBusinessNotFound { account_id }
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn account_items(account_id: i32, pool: Arc<sqlx::PgPool>) -> Vec<db::LocalBusinessItem> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT
|
||||
local_business_items.id,
|
||||
local_business_items.local_business_id,
|
||||
local_business_items.name,
|
||||
local_business_items.price,
|
||||
local_business_items.item_order,
|
||||
local_business_items.picture_url
|
||||
FROM local_business_items
|
||||
INNER JOIN local_businesses
|
||||
ON local_businesses.id = local_business_items.local_business_id
|
||||
WHERE local_businesses.owner_id = $1
|
||||
ORDER BY item_order ASC
|
||||
"#,
|
||||
)
|
||||
.bind(account_id)
|
||||
.fetch_all(&*pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("{e}");
|
||||
dbg!(&e);
|
||||
e
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::{encrypt, validate};
|
||||
|
Loading…
Reference in New Issue
Block a user