diff --git a/assets/css/app.css b/assets/css/app.css index 90ab6ce..e51c50c 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,4 +1,5 @@ -@import url('https://fonts.cdnfonts.com/css/noto-sans'); +@import url('/assets/css/noto-sans.css'); +@import url("/assets/css/beam-weapon.css"); * { --hover-color: #f18902; @@ -6,46 +7,60 @@ --border-light-gray-color: #e5e5e5; } +header > h1 { + display: none; +} + +@media (min-width: 1200px) { + header > h1 { + font-family: 'Beam Weapon', sans-serif; + font-weight: bold; + text-shadow: 2px 1px 2px #fff, -1px -1px 2px #fff; + display: block; + font-size: 115px; + padding: 0; + text-align: center; + background: no-repeat center url("/assets/images/background.webp"); + background: no-repeat center image-set(url("/assets/images/background.webp") 1x, url("/assets/images/background.jpeg") 1x); + border-radius: 4px; + background-size: 1280px 200px; + width: 1280px; + height: 200px; + margin: auto; + } + + article { + width: 1280px; + margin: auto auto; + padding: 0; + } + + ow-nav > ow-path > div { + display: block; + } +} + main { font-family: 'Noto Sans', sans-serif; } -* { - font-family: 'Noto Sans', sans-serif; +ow-nav > ow-path { + text-align: center; + width: 48px; +} +ow-nav > ow-path > div { + display: none; } -@media (min-width: 1200px) { - article { - width: 1280px; - margin: auto auto; - } +ow-nav > ow-path > svg { + fill: black; + min-width: 32px; + max-width: 48px; + margin: auto; +} - .bg { - height: 200px; - display: flex; - justify-content: space-between; - } - - .bg::after { - display: block; - text-align: center; - background: linear-gradient(90deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 1%, rgba(255, 255, 255, 0) 100%), - no-repeat center image-set(url("/assets/images/background.webp") 1x, url("/assets/images/background.jpeg") 1x); - height: 200px; - width: calc(100% - 300px); - max-width: 1200px; - content: ' '; - } - - .bg h1 { - font-weight: bold; - font-size: 50px; - text-shadow: 2px 2px 2px #c5d1d8; - text-align: center; - display: block; - width: 300px; - line-height: 4; - } +article { + padding: 4px; } * { @@ -77,7 +92,7 @@ h3 { } p { - font-size: 1.3em; + font-size: 1rem; line-height: 1.5; margin-bottom: 1.3em; } @@ -104,3 +119,33 @@ blockquote p { local-businesses local-business p { font-family: 'Noto Sans', sans-serif; } + +.error { + width: 1280px; + color: #ba3939; + background: #ffe0e0; + border: 1px solid #a33a3a; + margin: 8px auto auto; + padding: 8px; +} +#home { + position: relative; +} + +#home span { + position: absolute; + display: block; + left: 0; + bottom: -3px; + font-size: 8px; + width: 60px; + text-align: center; +} + +ow-nav > ow-path[selected="selected"] { + border: none; +} + +ow-nav > ow-path[selected="selected"] > svg { + fill: var(--hover-color); +} diff --git a/assets/css/beam-weapon.css b/assets/css/beam-weapon.css new file mode 100644 index 0000000..c80e926 --- /dev/null +++ b/assets/css/beam-weapon.css @@ -0,0 +1,98 @@ +@font-face { + font-family: 'Beam Weapon'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon'), url('https://fonts.cdnfonts.com/s/72008/beamweapon.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon 3D'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon 3D'), url('https://fonts.cdnfonts.com/s/72008/beamweapon3d.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Gradient'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Gradient'), url('https://fonts.cdnfonts.com/s/72008/beamweapongrad.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Halftone'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Halftone'), url('https://fonts.cdnfonts.com/s/72008/beamweaponhalf.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Laser'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Laser'), url('https://fonts.cdnfonts.com/s/72008/beamweaponlaser.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon 3D Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon 3D Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweapon3dital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Condensed'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Condensed'), url('https://fonts.cdnfonts.com/s/72008/beamweaponcond.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Condensed Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Condensed Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponcondital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Expanded'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Expanded'), url('https://fonts.cdnfonts.com/s/72008/beamweaponexpand.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Expanded Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Expanded Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponexpandital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Gradient Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Gradient Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweapongradital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Halftone Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Halftone Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponhalfital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Laser Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Laser Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponlaserital.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Leftalic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Leftalic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponleft.woff') format('woff'); +} +@font-face { + font-family: 'Beam Weapon Super-Italic'; + font-style: normal; + font-weight: 400; + src: local('Beam Weapon Super-Italic'), url('https://fonts.cdnfonts.com/s/72008/beamweaponsuperital.woff') format('woff'); +} + + diff --git a/assets/css/noto-sans.css b/assets/css/noto-sans.css new file mode 100644 index 0000000..18e42a0 --- /dev/null +++ b/assets/css/noto-sans.css @@ -0,0 +1,26 @@ +@font-face { + font-family: 'Noto Sans'; + font-style: normal; + font-weight: 400; + src: local('Noto Sans'), url('https://fonts.cdnfonts.com/s/15794/NotoSans-Regular.woff') format('woff'); +} +@font-face { + font-family: 'Noto Sans'; + font-style: italic; + font-weight: 400; + src: local('Noto Sans'), url('https://fonts.cdnfonts.com/s/15794/NotoSans-Italic.woff') format('woff'); +} +@font-face { + font-family: 'Noto Sans'; + font-style: normal; + font-weight: 700; + src: local('Noto Sans'), url('https://fonts.cdnfonts.com/s/15794/NotoSans-Bold.woff') format('woff'); +} +@font-face { + font-family: 'Noto Sans'; + font-style: italic; + font-weight: 700; + src: local('Noto Sans'), url('https://fonts.cdnfonts.com/s/15794/NotoSans-BoldItalic.woff') format('woff'); +} + + diff --git a/assets/images/background.jpeg b/assets/images/background.jpeg index 44d2333..05e122f 100644 Binary files a/assets/images/background.jpeg and b/assets/images/background.jpeg differ diff --git a/assets/images/background.webp b/assets/images/background.webp index 00fc4cb..6a49349 100644 Binary files a/assets/images/background.webp and b/assets/images/background.webp differ diff --git a/assets/templates/base.html b/assets/templates/base.html index 1e78857..76ab261 100644 --- a/assets/templates/base.html +++ b/assets/templates/base.html @@ -8,25 +8,11 @@ - + {% block head %}{% endblock %}
-
-
-

OS Wilno

-
-
{% match error %} {% when Some with (e) %}

{{e}}

@@ -34,23 +20,90 @@ {% endmatch %}
+ + + + + + + OS Wilno + + {% if page.is_public() %} - Lokalne Usługi - Targ - Aktualności - Konto - {% match account.as_ref() %} - {% when Some with (a) %} - Moje usługi - {% when None %} - {% endmatch %} - {% if h.is_admin(account) %} - Admin - {% endif %} + + + + + +
Lokalne Usługi
+
+ + + + + + + +
Targ
+
+ + + + + + +
Aktualności
+
+ + + + +
Konto
+
+ + {% if h.is_above_user(account) %} + + + + + +
Moje usługi
+
+ {% endif %} + + {% if h.is_admin(account) %} + + + + + + + + +
Admin
+
+ {% endif %} + {% else if page.is_admin() %} - Home - News - Localne Usługi + + + + + + +
Aktualności
+
+ + + + + +
Lokalne Usługi
+
{% endif %}
{% block content %}{% endblock %} diff --git a/assets/templates/business-items.html b/assets/templates/business-items.html deleted file mode 100644 index a2cb49c..0000000 --- a/assets/templates/business-items.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% block content %} - - {% for item in items %} - - - {% endfor %} - - -{% endblock %} diff --git a/assets/templates/businesses/editor.html b/assets/templates/businesses/editor.html new file mode 100644 index 0000000..917dbfa --- /dev/null +++ b/assets/templates/businesses/editor.html @@ -0,0 +1,35 @@ +{% extends "../base.html" %} +{% block content %} + + + + {% for contact in contacts %} + + + + {% endfor %} + + + + + {% for item in items %} + + + {% endfor %} + + +{% endblock %} diff --git a/assets/templates/businesses/index.html b/assets/templates/businesses/index.html new file mode 100644 index 0000000..d82a852 --- /dev/null +++ b/assets/templates/businesses/index.html @@ -0,0 +1,37 @@ +{% extends "../base.html" %} +{% block content %} + + {% for business in businesses %} + + {% for line in business.description.lines() %} +

{{line}}

+ {% endfor %} + + {% for item in business.items %} + + + {% endfor %} + + {% for contact in business.contacts %} + + {% endfor %} + + +
+ {% endfor %} +
+{% endblock %} diff --git a/assets/templates/index.html b/assets/templates/index.html deleted file mode 100644 index fe937b6..0000000 --- a/assets/templates/index.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "base.html" %} -{% block content %} - - {% for service in services %} - - {% for line in service.description.lines() %} -

{{line}}

- {% endfor %} - - {% for item in service.items %} - - - {% endfor %} - -
- {% endfor %} -
-{% endblock %} diff --git a/assets/templates/marketplace/index.html b/assets/templates/marketplace/index.html new file mode 100644 index 0000000..7e6b21c --- /dev/null +++ b/assets/templates/marketplace/index.html @@ -0,0 +1,3 @@ +{% extends "../base.html" %} +{% block content %} +{% endblock %} diff --git a/client/src/admin/admin-business.js b/client/src/admin/admin-business.js index a7e8ad4..e625dea 100644 --- a/client/src/admin/admin-business.js +++ b/client/src/admin/admin-business.js @@ -13,6 +13,7 @@ customElements.define('admin-business', class extends Component { } ::slotted(local-business-item) { width: 100%; + margin-bottom: .5rem; }
diff --git a/client/src/app.js b/client/src/app.js index 5990ea0..8c30b0c 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -2,16 +2,27 @@ import "./shared/form-navigation.js"; import "./local-businesses.js"; import "./login-form.js"; import "./ow-account.js"; + import "./nav/ow-nav.js"; import "./nav/ow-path.js"; + import "./price/price-view"; import "./price/price-input"; + import "./register-form.js"; -import "./business-items"; + +import "./business-items/business-item"; +import "./business-items/business-item-editor"; + import "./news/ow-articles"; import "./news/news-article"; + import "./shared/rich-text-editor"; +import "./contacts/contact-info-list"; +import "./contacts/contact-info"; +import "./contacts/contact-info-editor"; + import { fireFbReady } from "./shared.js"; if (!document.querySelector('#facebook-jssdk')) { diff --git a/client/src/business-items.js b/client/src/business-items/business-item-editor.js similarity index 96% rename from client/src/business-items.js rename to client/src/business-items/business-item-editor.js index cd77f0b..f166237 100644 --- a/client/src/business-items.js +++ b/client/src/business-items/business-item-editor.js @@ -1,9 +1,8 @@ -import { Component, FORM_STYLE } from "./shared"; +import { Component, FORM_STYLE } from "../shared"; -import "./business-items/business-item"; -import "./register-form/register-item-form-row"; +import "../register-form/register-item-form-row"; -customElements.define('business-items', class extends Component { +customElements.define('business-item-editor', class extends Component { #idx; static get observedAttributes() { diff --git a/client/src/business-items/business-item.js b/client/src/business-items/business-item.js index e6e31df..afbd7a7 100644 --- a/client/src/business-items/business-item.js +++ b/client/src/business-items/business-item.js @@ -10,17 +10,6 @@ customElements.define('business-item', class extends Component { super(` +
+
+
+ + +
+
+ +
+ + + + + + + + + + + +
+
+
+ +
+
+
+
+ +
+ `); + + this.shadowRoot.querySelector('#contact_type').addEventListener('change', ev => { + ev.stopPropagation(); + this.type = ev.target.value; + }); + } + + get type() { + return this.getAttribute('type'); + } + + set type(v) { + this.setAttribute('type', v); + this.shadowRoot.querySelector('#content').setAttribute('type', v === 'email' ? 'email' : 'text'); + } + + get content() { + return this.getAttribute('content'); + } + + set content(v) { + this.setAttribute('content', v); + this.shadowRoot.querySelector('#content').value = v; + } + + get contact_id() { + return this.getAttribute('contact-id'); + } + + set contact_id(v) { + this.setAttribute('contact-id', v); + const n = parseInt(v); + if (isNaN(n)) + this.#removeId(); + else + this.#addId(n); + } + + #removeId() { + const form = this.shadowRoot.querySelector('form'); + const input = form.querySelector('#contact-id'); + input && input.remove(); + form.action = '/contacts/create'; + } + + #addId(v) { + this.#removeId(); + const form = this.shadowRoot.querySelector('form'); + const input = form.appendChild(document.createElement('input')); + input.setAttribute('type', 'hidden'); + input.setAttribute('name', 'id'); + input.setAttribute('id', 'contact-id'); + input.value = v; + form.action = '/contacts/update'; + } +}); diff --git a/client/src/contacts/contact-info-list.js b/client/src/contacts/contact-info-list.js new file mode 100644 index 0000000..14ab3a6 --- /dev/null +++ b/client/src/contacts/contact-info-list.js @@ -0,0 +1,12 @@ +import { Component } from "../shared"; + +customElements.define('contact-info-list', class extends Component { + constructor() { + super(` + + + `); + } +}); diff --git a/client/src/contacts/contact-info.js b/client/src/contacts/contact-info.js new file mode 100644 index 0000000..0b84400 --- /dev/null +++ b/client/src/contacts/contact-info.js @@ -0,0 +1,12 @@ +import { Component } from "../shared"; + +customElements.define('contact-info', class extends Component { + constructor() { + super(` + + + `); + } +}); diff --git a/client/src/contacts/edit-contact-info.js b/client/src/contacts/edit-contact-info.js new file mode 100644 index 0000000..7987bdf --- /dev/null +++ b/client/src/contacts/edit-contact-info.js @@ -0,0 +1,38 @@ +import { Component } from "../shared"; + +customElements.define('edit-contact-info', class extends Component { + static get observedAttributes() { + return ['contact-id']; + } + + constructor() { + super(` + +
+ +
+ +
+ + +
+
+
+ `); + } + + get contact_id() { + return this.getAttribute('contact-id'); + } + + set contact_id(v) { + this.setAttribute('contact-id', v); + this.shadowRoot.querySelector('#remove-id').value = v; + } +}); diff --git a/client/src/local-businesses.js b/client/src/local-businesses.js index c424a0c..544f842 100644 --- a/client/src/local-businesses.js +++ b/client/src/local-businesses.js @@ -2,7 +2,7 @@ import "./local-businesses/local-business-item"; import "./local-businesses/local-business"; import { Component } from "./shared"; -customElements.define('local-businesses', class extends Component { +customElements.define('local-business-list', class extends Component { static get observedAttributes() { return ['filter'] } @@ -16,7 +16,7 @@ customElements.define('local-businesses', class extends Component { display: none; } input { - font-size: 20pt; + font-size: 1rem; line-height: 2.6em; height: 2.6em; margin: 0; @@ -34,7 +34,7 @@ customElements.define('local-businesses', class extends Component {
- +
`); { diff --git a/client/src/local-businesses/local-business-item.js b/client/src/local-businesses/local-business-item.js index 9302a35..ea10d29 100644 --- a/client/src/local-businesses/local-business-item.js +++ b/client/src/local-businesses/local-business-item.js @@ -10,23 +10,52 @@ customElements.define('local-business-item', class extends Component {
diff --git a/client/src/nav/ow-path.js b/client/src/nav/ow-path.js index 0b1af1f..ee441ee 100644 --- a/client/src/nav/ow-path.js +++ b/client/src/nav/ow-path.js @@ -14,16 +14,15 @@ customElements.define('ow-path', class extends Component { display: block; padding: 10px 18px; text-decoration: none; - color: #495057; text-transform: uppercase; border: none; - border-bottom: 1px solid var(--border-slim-color); + color: var(--hover-color); } a:hover { color: var(--hover-color); } :host(:not([selected])) a { - border: none; + color: #495057; } diff --git a/client/src/news/news-article.js b/client/src/news/news-article.js index 1c1834c..443d60a 100644 --- a/client/src/news/news-article.js +++ b/client/src/news/news-article.js @@ -20,10 +20,6 @@ customElements.define('news-article', class extends Component { h1 #status { font-size: 14px; } - #time { - display: flex; - justify-content: space-between; - } .time span:first-child { margin-right: 8px; } @@ -41,6 +37,12 @@ customElements.define('news-article', class extends Component { :host([hide-status="true"]) #status { display: none; } + @media (min-width: 1200px) { + #time { + display: flex; + justify-content: space-between; + } + } ${ BLOCK_QUOTE_STYLE }
diff --git a/client/src/shared.js b/client/src/shared.js index 016ad41..bb1813c 100644 --- a/client/src/shared.js +++ b/client/src/shared.js @@ -1,7 +1,7 @@ export const S = Symbol(); export const BUTTON_STYLE = ` -input[type="button"], input[type="submit"] { +input[type="button"], input[type="submit"], button { cursor: pointer; border-radius: 5px; box-shadow: 0 10px 20px -6px rgba(0,0,0,.12); diff --git a/migrations/20220715071927_add_offers.sql b/migrations/20220715071927_add_offers.sql new file mode 100644 index 0000000..fedb40e --- /dev/null +++ b/migrations/20220715071927_add_offers.sql @@ -0,0 +1,14 @@ +CREATE TYPE "OfferState" AS ENUM ( + 'Pending', + 'Approved', + 'Banned' + ); + +CREATE TABLE offers +( + id serial unique not null primary key, + name text not null, + picture_url text not null, + state "OfferState" not null, + created_at timestamp not null default now() +); diff --git a/migrations/20220715115332_add_contacts.sql b/migrations/20220715115332_add_contacts.sql new file mode 100644 index 0000000..5ef93d6 --- /dev/null +++ b/migrations/20220715115332_add_contacts.sql @@ -0,0 +1,7 @@ +CREATE TABLE contacts +( + id serial not null primary key unique, + owner_id int not null references accounts (id), + contact_type text not null, + content text not null +); diff --git a/src/model/db.rs b/src/model/db.rs index 3554bfb..4dacf42 100644 --- a/src/model/db.rs +++ b/src/model/db.rs @@ -8,9 +8,9 @@ use uuid::Uuid; #[derive(Debug, PartialOrd, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Type)] pub enum AccountType { - User, - Business, - Admin, + User = 1, + Business = 10, + Admin = 100, } #[derive(Debug, Serialize, Deserialize, FromRow)] @@ -108,6 +108,14 @@ pub struct LocalBusinessItem { pub picture_url: String, } +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct ContactInfo { + pub id: i32, + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} + #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct NewsArticle { pub id: i32, @@ -177,3 +185,18 @@ pub struct UpdateLocalBusinessItemInput { pub struct DeleteNewsArticleInput { pub id: i32, } + +#[derive(Debug)] +pub struct CreateContactInput { + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} + +#[derive(Debug)] +pub struct UpdateContactInput { + pub id: i32, + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} diff --git a/src/model/view.rs b/src/model/view.rs index 04faa75..a6e8181 100644 --- a/src/model/view.rs +++ b/src/model/view.rs @@ -116,10 +116,23 @@ pub struct LocalBusiness { pub description: String, pub state: db::LocalBusinessState, pub items: Vec, + pub contacts: Vec, } -impl<'v> From<(db::LocalBusiness, &'v mut Vec)> for LocalBusiness { - fn from((service, items): (db::LocalBusiness, &'v mut Vec)) -> Self { +impl<'items, 'contacts> + From<( + db::LocalBusiness, + &'items mut Vec, + &'contacts mut Vec, + )> for LocalBusiness +{ + fn from( + (service, items, contacts): ( + db::LocalBusiness, + &'items mut Vec, + &'contacts mut Vec, + ), + ) -> Self { Self { id: service.id, owner_id: service.owner_id, @@ -129,6 +142,9 @@ impl<'v> From<(db::LocalBusiness, &'v mut Vec)> for Local items: items .drain_filter(|i| i.local_business_id == service.id) .collect(), + contacts: contacts + .drain_filter(|c| c.owner_id == service.owner_id) + .collect(), } } } diff --git a/src/queries/mod.rs b/src/queries/mod.rs index f3d3641..aba705f 100644 --- a/src/queries/mod.rs +++ b/src/queries/mod.rs @@ -29,6 +29,10 @@ pub enum Error { id: i32, item_order: i32, }, + BusinessItemState { + id: i32, + state: db::LocalBusinessState, + }, AllItems, OwnedBusiness { account_id: i32, @@ -53,6 +57,19 @@ pub enum Error { DeleteNewsArticle { id: i32, }, + AllContacts, + AccountContacts { + account_id: i32, + }, + DeleteContact { + id: i32, + }, + CreateContact { + input: db::CreateContactInput, + }, + UpdateContact { + input: db::UpdateContactInput, + }, } pub type Result = std::result::Result; @@ -749,6 +766,137 @@ RETURNING .map_err(|e| { error!("{e}"); dbg!(&e); - Error::VisibleBusinessItems + Error::BusinessItemState { id, state } + }) +} + +#[tracing::instrument] +pub async fn all_contacts(t: &mut T<'_>) -> Result> { + sqlx::query_as( + r#" +SELECT + id, + owner_id, + contact_type, + content +FROM + contacts + "#, + ) + .fetch_all(t) + .await + .map_err(|e| { + error!("{e}"); + dbg!(&e); + Error::AllContacts + }) +} + +#[tracing::instrument] +pub async fn account_contacts(t: &mut T<'_>, account_id: i32) -> Result> { + sqlx::query_as( + r#" +SELECT + id, + owner_id, + contact_type, + content +FROM + contacts +WHERE + owner_id = $1 + "#, + ) + .bind(account_id) + .fetch_all(t) + .await + .map_err(|e| { + error!("{e}"); + dbg!(&e); + Error::AccountContacts { account_id } + }) +} + +#[tracing::instrument] +pub async fn create_contact( + t: &mut T<'_>, + input: db::CreateContactInput, +) -> Result> { + sqlx::query_as( + r#" +INSERT INTO contacts ( owner_id, contact_type, content ) +VALUES ($1, $2, $3) +RETURNING + id, + owner_id, + contact_type, + content + "#, + ) + .bind(input.owner_id) + .bind(&input.contact_type) + .bind(&input.content) + .fetch_all(t) + .await + .map_err(|e| { + error!("{e}"); + dbg!(&e); + Error::CreateContact { input } + }) +} + +#[tracing::instrument] +pub async fn update_contact( + t: &mut T<'_>, + input: db::UpdateContactInput, +) -> Result> { + sqlx::query_as( + r#" +UPDATE contacts +VALUES + owner_id = $2, + contact_type = $3, + content = $4 +WHERE id = $1 +RETURNING + id, + owner_id, + contact_type, + content + "#, + ) + .bind(input.id) + .bind(input.owner_id) + .bind(&input.contact_type) + .bind(&input.content) + .fetch_all(t) + .await + .map_err(|e| { + error!("{e}"); + dbg!(&e); + Error::UpdateContact { input } + }) +} + +#[tracing::instrument] +pub async fn delete_contact(t: &mut T<'_>, id: i32) -> Result> { + sqlx::query_as( + r#" +DELETE FROM contacts +WHERE id = $1 +RETURNING + id, + owner_id, + contact_type, + content + "#, + ) + .bind(id) + .fetch_all(t) + .await + .map_err(|e| { + error!("{e}"); + dbg!(&e); + Error::DeleteContact { id } }) } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index f41460c..20e694f 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -103,9 +103,9 @@ pub enum Error { pub fn reject_xss(s: &str) -> Result<()> { if s.contains("