1
0

add rudimentary adding and listing of objects

This commit is contained in:
Rokas Puzonas 2023-04-02 23:33:24 +03:00
parent cead02abe8
commit e3dd339ffe
8 changed files with 299 additions and 88 deletions

Binary file not shown.

View File

@ -45,9 +45,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.69"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "async-attributes"
@ -1406,6 +1406,7 @@ checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
name = "ice-production"
version = "0.0.0"
dependencies = [
"anyhow",
"async-std",
"dotenv",
"serde",

View File

@ -19,6 +19,7 @@ sqlx = { version = "0.6.2", features = ["runtime-async-std-native-tls", "mysql",
async-std = {version = "1.12.0", features = ["attributes"] }
serde_json = "1.0"
dotenv = "0.15.0"
anyhow = "1.0.70"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

View File

@ -5,11 +5,16 @@ use std::{env, process::exit};
use dotenv::dotenv;
use serde::{Serialize, Deserialize};
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Pool, Sqlite};
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions, Pool, Sqlite, Row};
use tauri::State;
// TODO: use transaction to revert changes, if failed to insert rows
type Id = i64;
#[derive(Serialize)]
struct Manager {
id: i64,
id: Id,
first_name: String,
surname: String,
phone_number: Option<String>,
@ -17,42 +22,114 @@ struct Manager {
email: Option<String>
}
#[derive(Deserialize)]
struct ManagerData {
first_name: String,
surname: String,
phone_number: Option<String>,
title: String,
email: Option<String>
}
#[derive(Serialize)]
struct Factory {
id: Id,
name: String,
location: String,
floor_size: f64,
manager_id: Id
}
#[derive(Deserialize)]
struct FactoryData {
name: String,
location: String,
floor_size: f64
}
struct Database {
pool: Pool<Sqlite>
}
#[derive(Serialize, Deserialize)]
enum AddResponse {
Success(i64)
struct EntryID {
id: Id
}
async fn add_manager(db: &Database, manager: &ManagerData) -> anyhow::Result<Id> {
let entry = sqlx::query_as!(
EntryID,
r#"
INSERT INTO manager
(`FIRST_NAME`, `SURNAME`, `PHONE_NUMBER`, `TITLE`, `EMAIL`)
VALUES
(?, ?, ?, ?, ?)
RETURNING ID as id
"#,
manager.first_name,
manager.surname,
manager.phone_number,
manager.title,
manager.email
).fetch_one(&db.pool).await?;
Ok(entry.id)
}
async fn add_factory(db: &Database, manager_id: Id, factory: &FactoryData) -> anyhow::Result<Id> {
let entry = sqlx::query_as!(
EntryID,
r#"
INSERT INTO factory
(`NAME`, `LOCATION`, `FLOOR_SIZE`, `FK_MANAGER_ID`)
VALUES
(?, ?, ?, ?)
RETURNING ID as id
"#,
factory.name,
factory.location,
factory.floor_size,
manager_id
).fetch_one(&db.pool).await?;
Ok(entry.id)
}
async fn list_factories_db(db: &Database) -> anyhow::Result<Vec<Factory>> {
let factories = sqlx::query_as!(
Factory,
r#"
SELECT ID as id, NAME as name, LOCATION as location, FLOOR_SIZE as floor_size, FK_MANAGER_ID as manager_id FROM factory
"#,
).fetch_all(&db.pool).await?;
Ok(factories)
}
async fn list_managers_db(db: &Database) -> anyhow::Result<Vec<Manager>> {
let managers = sqlx::query_as!(
Manager,
r#"
SELECT ID as id, FIRST_NAME as first_name, SURNAME as surname, TITLE as title, EMAIL as email, PHONE_NUMBER as phone_number FROM manager
"#,
).fetch_all(&db.pool).await?;
Ok(managers)
}
#[tauri::command]
async fn add_manager(
first_name: &str,
surname: &str,
phone_number: Option<&str>,
title: &str,
email: Option<&str>,
database: State<'_, Database>
) -> Result<AddResponse, ()> {
let id = sqlx::query_as!(
Manager,
r#"
INSERT INTO manager
(`FIRST_NAME`, `SURNAME`, `PHONE_NUMBER`, `TITLE`, `EMAIL`)
VALUES
(?, ?, ?, ?, ?)
"#,
first_name,
surname,
phone_number,
title,
email
).execute(&database.pool)
.await.map_err(|_| ())?
.last_insert_rowid();
async fn add_manager_factory(
factory: FactoryData,
manager: ManagerData,
db: State<'_, Database>
) -> Result<(Id, Id), ()> {
let manager_id = add_manager(&db, &manager).await.unwrap();
let factory_id = add_factory(&db, manager_id, &factory).await.unwrap();
Ok((factory_id, manager_id))
}
Ok(AddResponse::Success(id))
#[tauri::command]
async fn list_managers(db: State<'_, Database>) -> Result<Vec<Manager>, ()> {
Ok(list_managers_db(&db).await.unwrap()) // TODO: handle .unwrap()
}
#[tauri::command]
async fn list_factories(db: State<'_, Database>) -> Result<Vec<Factory>, ()> {
Ok(list_factories_db(&db).await.unwrap()) // TODO: handle .unwrap()
}
#[async_std::main]
@ -76,7 +153,9 @@ async fn main() {
tauri::Builder::default()
.manage(Database { pool })
.invoke_handler(tauri::generate_handler![
add_manager
list_factories,
list_managers,
add_manager_factory
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@ -1,11 +1,12 @@
<script lang="ts">
import { Form, TextInput, NumberInput } from "carbon-components-svelte";
import type { FactoryData } from "./api"
export const factory = {
export let factory: FactoryData = {
name: "",
location: "",
floorSize: 1
floor_size: 1
}
// Name validation
@ -24,13 +25,13 @@
export const validate = () => {
validateName()
validateLocation()
return !showLocationInvalid && !showNameInvalid && factory.floorSize >= 1
return !showLocationInvalid && !showNameInvalid && factory.floor_size >= 1
};
export const reset = () => {
factory.name = ""
factory.location = ""
factory.floorSize = 1
factory.floor_size = 1
showNameInvalid = false
showLocationInvalid = false
};
@ -54,7 +55,7 @@
labelText="Location"
/>
<NumberInput
bind:value={factory.floorSize}
bind:value={factory.floor_size}
required
hideSteppers
min={1}

View File

@ -1,18 +1,19 @@
<script lang="ts">
import { Form, TextInput } from "carbon-components-svelte";
import type { ManagerData } from "./api";
export let manager = {
firstName: "",
surname: "",
phoneNumber: "",
title: "",
email: ""
export let manager: ManagerData = {
first_name: "",
surname: "",
phone_number: "",
title: "",
email: ""
}
// First name validation
let showFirstNameInvalid = false;
function validateFirstName() {
showFirstNameInvalid = manager.firstName === ""
showFirstNameInvalid = manager.first_name === ""
}
// Surname validation
@ -36,9 +37,9 @@
};
export const reset = () => {
manager.firstName = ""
manager.first_name = ""
manager.surname = ""
manager.phoneNumber = ""
manager.phone_number = ""
manager.title = ""
manager.email = ""
showFirstNameInvalid = false
@ -50,7 +51,7 @@
<Form class="flex flex-col gap-1rem" title="Manager">
<TextInput
bind:value={manager.firstName}
bind:value={manager.first_name}
invalidText={"First name can't be empty"}
bind:invalid={showFirstNameInvalid}
on:input={validateFirstName}
@ -74,7 +75,7 @@
labelText="Title"
/>
<TextInput
bind:value={manager.phoneNumber}
bind:value={manager.phone_number}
labelText="Phone number"
/>
<TextInput

72
lab2/src/lib/api.ts Normal file
View File

@ -0,0 +1,72 @@
import { invoke } from '@tauri-apps/api/tauri'
import { writable } from 'svelte/store'
export interface Manager {
id: number,
first_name: string,
surname: string,
phone_number?: string,
title: string,
email?: string
}
export interface ManagerData {
first_name: string,
surname: string,
phone_number?: string,
title: string,
email?: string
}
export interface Factory {
id: number,
name: string,
location: string,
floor_size: number,
manager_id: number
}
export interface FactoryData {
name: string,
location: string,
floor_size: number
}
export let factories = writable<Factory[]>([])
export let managers = writable<Manager[]>([])
export async function list_factories(): Promise<Factory[]> {
return invoke("list_factories")
}
export async function list_managers(): Promise<Manager[]> {
return invoke("list_managers")
}
// Result -> Promise<[factory_id, manager_id]>
export async function add_manager_factory(factory: FactoryData, manager: ManagerData): Promise<[number, number]> {
// TODO: For now always assume success
// TODO: handle error
let [factory_id, manager_id] = await invoke<[number, number]>("add_manager_factory", { factory, manager })
factories.update((factories) => {
factories.push({
id: factory_id,
manager_id,
...factory
})
return factories
})
managers.update((managers) => {
managers.push({ id: manager_id, ...manager })
return managers
})
return [factory_id, manager_id]
}
export async function edit_manager(id: number, manager: ManagerData): Promise<void> {
// TODO: invoke("edit_manager")
}
export async function edit_factory(id: number, factory: FactoryData): Promise<void> {
// TODO: invoke("edit_factory")
}

View File

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import {
DataTable,
Toolbar,
@ -8,40 +8,73 @@
ToolbarSearch,
Pagination,
Modal,
} from "carbon-components-svelte";
import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte";
import Edit from "carbon-icons-svelte/lib/Edit.svelte";
import ManagerForm from "../lib/ManagerForm.svelte";
import FactoryForm from "../lib/FactoryForm.svelte";
} from "carbon-components-svelte"
import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte"
import Edit from "carbon-icons-svelte/lib/Edit.svelte"
import ManagerForm from "../lib/ManagerForm.svelte"
import FactoryForm from "../lib/FactoryForm.svelte"
import { add_manager_factory, edit_factory, edit_manager, factories, list_factories, list_managers, managers } from "../lib/api"
import { onMount } from 'svelte'
let rows = [
{ id: "a", name: "Load Balancer 3", port: 3000, rule: "Round robin" },
{ id: "b", name: "Load Balancer 1", port: 443, rule: "Round robin" },
{ id: "c", name: "Load Balancer 2", port: 80, rule: "DNS delegation" },
{ id: "d", name: "Load Balancer 6", port: 3000, rule: "Round robin" },
{ id: "e", name: "Load Balancer 4", port: 443, rule: "Round robin" },
{ id: "f", name: "Load Balancer 5", port: 80, rule: "DNS delegation" },
];
var rows = []
onMount(async () => {
$managers = await list_managers()
$factories = await list_factories()
console.log($managers)
})
$: {
console.log($factories)
rows = $factories.map(factory => {
let manager = $managers.find(m => m.id == factory.manager_id)
return {
id: factory.id,
factory_name: factory.name,
factory_location: factory.location,
factory_floor_size: factory.floor_size,
manager_fullname: `${manager.first_name} ${manager.surname}`
}
})
}
let isModalShown = false
let resetFactoryForm, validateFactoryForm, factoryData
let resetManagerForm, validateManagerForm, managerData
let activeDelete = false
let activeUpdate = false
let selectedRowIds = []
let filteredRowIds = []
let resetFactoryForm, validateFactoryForm;
let resetManagerForm, validateManagerForm;
let showCreateModal = false;
$: if (showCreateModal) {
resetFactoryForm()
resetManagerForm()
let currentlyEditingId = undefined
let showUpdateModal = factory_id => {
currentlyEditingId = factory_id
let factory = $factories.find(factory => factory.id == factory_id)
let manager = $managers.find(manager => manager.id == factory.manager_id)
factoryData.name = factory.name
factoryData.location = factory.location
factoryData.floor_size = factory.floor_size
managerData.first_name = manager.first_name
managerData.surname = manager.surname
managerData.phone_number = manager.phone_number
managerData.title = manager.title
managerData.email = manager.email
isModalShown = true
}
let headers = [
{ key: "name", value: "Name" },
{ key: "port", value: "Port" },
{ key: "rule", value: "Rule" },
{ key: "update-btn", empty: true, width: "5rem" },
]
let showCreateModal = () => {
currentlyEditingId = undefined
resetFactoryForm()
resetManagerForm()
isModalShown = true
}
let closeModal = () => {
isModalShown = false
}
</script>
<DataTable
@ -50,7 +83,13 @@
batchSelection={activeDelete}
sortable
bind:selectedRowIds
{headers}
headers={[
{ key: "factory_name", value: "Factory" },
{ key: "factory_location", value: "Location" },
{ key: "factory_floor_size", value: "Floor size" },
{ key: "manager_fullname", value: "Manager" },
{ key: "update_btn", empty: true, width: "5rem" },
]}
{rows}
>
<Toolbar>
@ -78,13 +117,19 @@
shouldFilterRows
bind:filteredRowIds
/>
<Button on:click={() => showCreateModal = true}>Create</Button>
<Button on:click={() => (activeDelete = true)}>Delete</Button>
<Button on:click={showCreateModal}>Create</Button>
<Button on:click={() => activeDelete = true}>Delete</Button>
</ToolbarContent>
</Toolbar>
<svelte:fragment slot="cell" let:cell>
{#if cell.key === "update-btn"}
<Button kind="ghost" size="field" iconDescription={"Edit"} icon={Edit} />
<svelte:fragment slot="cell" let:row let:cell>
{#if cell.key === "update_btn"}
<Button
kind="ghost"
size="field"
iconDescription={"Edit"}
icon={Edit}
on:click={() => showUpdateModal(row.id)}
/>
{:else}
{cell.value}
{/if}
@ -92,21 +137,30 @@
</DataTable>
<Modal
bind:open={showCreateModal}
modalHeading="Create factory & manager"
bind:open={isModalShown}
modalHeading={currentlyEditingId ? "Edit factory & manager" : "Create factory & manager"}
primaryButtonText="Confirm"
secondaryButtonText="Cancel"
on:open
on:close={() => showCreateModal = false}
on:click:button--secondary={() => showCreateModal = false}
on:submit={() => {
on:click:button--secondary={closeModal}
on:submit={async () => {
let isFactoryValid = validateFactoryForm()
let isManagerValid = validateManagerForm()
if (!(isFactoryValid && isManagerValid)) {
return false
}
showCreateModal = false
if (currentlyEditingId) {
// TODO: Handle if factory is not found
let manager_id = $factories.find(factory => factory.id == currentlyEditingId).manager_id
await edit_factory(currentlyEditingId, factoryData) // TODO: handle if failed
await edit_manager(manager_id, managerData) // TODO: handle if failed
} else {
await add_manager_factory(factoryData, managerData) // TODO: handle error
}
closeModal()
return true
}}
>
@ -114,6 +168,7 @@
<div class="flex-grow">
<p>Factory</p>
<FactoryForm
bind:factory={factoryData}
bind:validate={validateFactoryForm}
bind:reset={resetFactoryForm}
/>
@ -121,6 +176,7 @@
<div class="flex-grow-2">
<p>Manager</p>
<ManagerForm
bind:manager={managerData}
bind:validate={validateManagerForm}
bind:reset={resetManagerForm}
/>