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 %}
-
{% 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("