1
0

Add process add/list/delete/update

This commit is contained in:
Rokas Puzonas 2023-04-14 21:17:00 +03:00
parent 77684d6347
commit e149726b83
14 changed files with 478 additions and 34 deletions

View File

@ -1,2 +1 @@
RUST_BACKTRACE=1
DATABASE_URL="mysql://root@localhost/ice_cream" DATABASE_URL="mysql://root@localhost/ice_cream"

View File

@ -1,5 +1,6 @@
{ {
"rust-analyzer.linkedProjects": [ "rust-analyzer.linkedProjects": [
".\\src-tauri\\Cargo.toml",
".\\src-tauri\\Cargo.toml" ".\\src-tauri\\Cargo.toml"
] ]
} }

View File

@ -1,4 +1,4 @@
use crate::{models::{Manager, Factory, ManagerData, FactoryData, Id}, manager_repo, factory_repo}; use crate::{models::{Manager, Factory, ManagerData, FactoryData, Id, Process, ProcessData}, manager_repo, factory_repo, process_repo};
use sqlx::{Pool, MySql}; use sqlx::{Pool, MySql};
use tauri::State; use tauri::State;
@ -21,12 +21,14 @@ pub async fn add_manager_factory(
Ok((factory_id, manager_id)) Ok((factory_id, manager_id))
} }
// --------------------- Factory ---------------------------
#[tauri::command] #[tauri::command]
pub async fn delete_factory(id: Id, db: State<'_, Database>) -> Result<()> { pub async fn list_factories(db: State<'_, Database>) -> Result<Vec<Factory>> {
let mut tx = db.pool.begin().await?; let mut tx = db.pool.begin().await?;
factory_repo::delete(&mut tx, id).await?; let factories = factory_repo::list(&mut tx).await?;
tx.commit().await?; tx.commit().await?;
Ok(()) Ok(factories)
} }
#[tauri::command] #[tauri::command]
@ -38,13 +40,15 @@ pub async fn update_factory(id: Id, factory: FactoryData, db: State<'_, Database
} }
#[tauri::command] #[tauri::command]
pub async fn update_manager(id: Id, manager: ManagerData, db: State<'_, Database>) -> Result<()> { pub async fn delete_factory(id: Id, db: State<'_, Database>) -> Result<()> {
let mut tx = db.pool.begin().await?; let mut tx = db.pool.begin().await?;
manager_repo::update(&mut tx, id, &manager).await?; factory_repo::delete(&mut tx, id).await?;
tx.commit().await?; tx.commit().await?;
Ok(()) Ok(())
} }
// --------------------- Manager ---------------------------
#[tauri::command] #[tauri::command]
pub async fn list_managers(db: State<'_, Database>) -> Result<Vec<Manager>> { pub async fn list_managers(db: State<'_, Database>) -> Result<Vec<Manager>> {
let mut tx = db.pool.begin().await?; let mut tx = db.pool.begin().await?;
@ -54,10 +58,47 @@ pub async fn list_managers(db: State<'_, Database>) -> Result<Vec<Manager>> {
} }
#[tauri::command] #[tauri::command]
pub async fn list_factories(db: State<'_, Database>) -> Result<Vec<Factory>> { pub async fn update_manager(id: Id, manager: ManagerData, db: State<'_, Database>) -> Result<()> {
let mut tx = db.pool.begin().await?; let mut tx = db.pool.begin().await?;
let factories = factory_repo::list(&mut tx).await?; manager_repo::update(&mut tx, id, &manager).await?;
tx.commit().await?; tx.commit().await?;
Ok(factories) Ok(())
} }
// --------------------- Process ---------------------------
#[tauri::command]
pub async fn list_processess(db: State<'_, Database>) -> Result<Vec<Process>> {
let mut tx = db.pool.begin().await?;
let processess = process_repo::list(&mut tx).await?;
tx.commit().await?;
Ok(processess)
}
#[tauri::command]
pub async fn delete_process(id: Id, db: State<'_, Database>) -> Result<()> {
let mut tx = db.pool.begin().await?;
process_repo::delete(&mut tx, id).await?;
tx.commit().await?;
Ok(())
}
#[tauri::command]
pub async fn update_process(id: Id, process: ProcessData, db: State<'_, Database>) -> Result<()> {
let mut tx = db.pool.begin().await?;
process_repo::update(&mut tx, id, &process).await?;
tx.commit().await?;
Ok(())
}
#[tauri::command]
pub async fn add_process(
process: ProcessData,
db: State<'_, Database>
) -> Result<Id> {
let mut tx = db.pool.begin().await?;
let id = process_repo::add(&mut tx, &process).await?;
tx.commit().await?;
Ok(id)
}

View File

@ -7,7 +7,7 @@ type MySqlTransaction<'a> = Transaction<'a, MySql>;
pub async fn create_table(tx: &mut MySqlTransaction<'_>) -> Result<()> { pub async fn create_table(tx: &mut MySqlTransaction<'_>) -> Result<()> {
sqlx::query(r#" sqlx::query(r#"
CREATE TABLE IF NOT EXISTS `factory` ( CREATE TABLE IF NOT EXISTS `factory` (
ID bigint unsigned NOT NULL, ID bigint unsigned NOT NULL AUTO_INCREMENT,
NAME varchar(255) NOT NULL, NAME varchar(255) NOT NULL,
LOCATION varchar(255) NOT NULL, LOCATION varchar(255) NOT NULL,
FLOOR_SIZE float NOT NULL, FLOOR_SIZE float NOT NULL,

View File

@ -4,12 +4,13 @@
mod models; mod models;
mod factory_repo; mod factory_repo;
mod manager_repo; mod manager_repo;
mod process_repo;
mod api; mod api;
use std::{env, process::exit}; use std::{env, process::exit};
use dotenv::dotenv; use dotenv::dotenv;
use models::{ManagerData, FactoryData}; use models::{ManagerData, FactoryData, ProcessData};
use sqlx::{Pool, MySql, mysql::MySqlPoolOptions, Row}; use sqlx::{Pool, MySql, mysql::MySqlPoolOptions, Row};
use api::*; use api::*;
@ -19,6 +20,7 @@ async fn setup_tables(pool: &Pool<MySql>) -> Result<()> {
let mut tx = pool.begin().await?; let mut tx = pool.begin().await?;
manager_repo::create_table(&mut tx).await?; manager_repo::create_table(&mut tx).await?;
factory_repo::create_table(&mut tx).await?; factory_repo::create_table(&mut tx).await?;
process_repo::create_table(&mut tx).await?;
tx.commit().await?; tx.commit().await?;
Ok(()) Ok(())
} }
@ -33,8 +35,12 @@ async fn drop_all_tables(pool: &Pool<MySql>) -> Result<()> {
Ok(()) Ok(())
} }
async fn enable_foreign_key_checks(pool: &Pool<MySql>) -> Result<()> { async fn set_foreign_key_checks(pool: &Pool<MySql>, enable: bool) -> Result<()> {
sqlx::query("SET GLOBAL FOREIGN_KEY_CHECKS=1").execute(pool).await?; let query = match enable {
true => "SET GLOBAL FOREIGN_KEY_CHECKS=1",
false => "SET GLOBAL FOREIGN_KEY_CHECKS=0",
};
sqlx::query(query).execute(pool).await?;
Ok(()) Ok(())
} }
@ -52,8 +58,13 @@ async fn add_test_data(pool: &Pool<MySql>) -> Result<()> {
location: "idk".into(), location: "idk".into(),
floor_size: 10.0, floor_size: 10.0,
}; };
let process = ProcessData {
name: "Certifuge 9000".into(),
size: 10.0
};
let id = manager_repo::add(&mut tx, &manager).await?; let id = manager_repo::add(&mut tx, &manager).await?;
factory_repo::add(&mut tx, id, &factory).await?; factory_repo::add(&mut tx, id, &factory).await?;
process_repo::add(&mut tx, &process).await?;
tx.commit().await?; tx.commit().await?;
Ok(()) Ok(())
@ -77,8 +88,9 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
enable_foreign_key_checks(&pool).await.expect("Enable foreign key checks"); set_foreign_key_checks(&pool, false).await.expect("Disable foreign key checks");
drop_all_tables(&pool).await.unwrap(); // For testing purposes drop_all_tables(&pool).await.unwrap(); // For testing purposes
set_foreign_key_checks(&pool, true).await.expect("Enable foreign key checks");
setup_tables(&pool).await.expect("Setup tables"); setup_tables(&pool).await.expect("Setup tables");
add_test_data(&pool).await.expect("Add test data"); add_test_data(&pool).await.expect("Add test data");
@ -86,12 +98,19 @@ async fn main() {
tauri::Builder::default() tauri::Builder::default()
.manage(Database { pool }) .manage(Database { pool })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
list_factories,
list_managers,
add_manager_factory, add_manager_factory,
list_factories,
delete_factory, delete_factory,
update_factory, update_factory,
update_manager
list_managers,
update_manager,
list_processess,
update_process,
delete_process,
add_process
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -9,12 +9,13 @@ type MySqlTransaction<'a> = Transaction<'a, MySql>;
pub async fn create_table(tx: &mut MySqlTransaction<'_>) -> Result<()> { pub async fn create_table(tx: &mut MySqlTransaction<'_>) -> Result<()> {
sqlx::query(r#" sqlx::query(r#"
CREATE TABLE IF NOT EXISTS `manager` ( CREATE TABLE IF NOT EXISTS `manager` (
ID bigint unsigned NOT NULL, ID bigint unsigned NOT NULL AUTO_INCREMENT,
FIRST_NAME varchar(255) NOT NULL, FIRST_NAME varchar(255) NOT NULL,
SURNAME varchar(255) NOT NULL, SURNAME varchar(255) NOT NULL,
PHONE_NUMBER varchar(255) NULL, PHONE_NUMBER varchar(255) NULL,
TITLE varchar(255) NOT NULL, TITLE varchar(255) NOT NULL,
EMAIL varchar(255) NULL, EMAIL varchar(255) NULL,
PRIMARY KEY(ID) PRIMARY KEY(ID)
);"#) );"#)
.execute(tx).await?; .execute(tx).await?;

View File

@ -36,4 +36,17 @@ pub struct FactoryData {
pub name: String, pub name: String,
pub location: String, pub location: String,
pub floor_size: f32 pub floor_size: f32
}
#[derive(Debug, Serialize, FromRow)]
pub struct Process {
pub id: Id,
pub name: String,
pub size: f32
}
#[derive(Debug, Deserialize)]
pub struct ProcessData {
pub name: String,
pub size: f32
} }

View File

@ -0,0 +1,83 @@
use anyhow::Result;
use sqlx::{Transaction, MySql};
use crate::models::{ProcessData, Id, Process};
type MySqlTransaction<'a> = Transaction<'a, MySql>;
pub async fn create_table(tx: &mut MySqlTransaction<'_>) -> Result<()> {
sqlx::query(r#"
CREATE TABLE IF NOT EXISTS `process` (
ID bigint unsigned NOT NULL AUTO_INCREMENT,
NAME varchar(255) NOT NULL,
SIZE float NOT NULL,
PRIMARY KEY(ID)
);"#)
.execute(&mut *tx).await?;
sqlx::query(r#"
CREATE TABLE `factory_supports_processes` (
FK_PROCESS_ID bigint unsigned NOT NULL,
FK_FACTORY_ID bigint unsigned NOT NULL,
PRIMARY KEY(FK_PROCESS_ID, FK_FACTORY_ID),
FOREIGN KEY(FK_PROCESS_ID) REFERENCES PROCESS (ID),
FOREIGN KEY(FK_FACTORY_ID) REFERENCES FACTORY (ID)
);"#)
.execute(&mut *tx).await?;
Ok(())
}
pub async fn add(tx: &mut MySqlTransaction<'_>, process: &ProcessData) -> Result<Id>
{
let id = sqlx::query(r#"
INSERT INTO `process`
(`NAME`, `SIZE`)
VALUES
(?, ?)
"#)
.bind(&process.name)
.bind(process.size)
.execute(&mut *tx).await?
.last_insert_id();
Ok(id)
}
pub async fn list(tx: &mut MySqlTransaction<'_>) -> Result<Vec<Process>> {
let factories = sqlx::query_as::<_, Process>(
r#"
SELECT
ID as id,
NAME as name,
SIZE as size
FROM `process`
"#).fetch_all(tx).await?;
Ok(factories)
}
pub async fn delete(tx: &mut MySqlTransaction<'_>, id: Id) -> Result<()> {
sqlx::query("DELETE FROM `process` WHERE ID = ?")
.bind(id)
.execute(&mut *tx).await?;
Ok(())
}
pub async fn update(tx: &mut MySqlTransaction<'_>, id: Id, process: &ProcessData) -> Result<()> {
sqlx::query(
r#"
UPDATE `process` SET
NAME = ?,
SIZE = ?
WHERE ID = ?
"#)
.bind(&process.name)
.bind(process.size)
.bind(id)
.execute(tx).await?;
Ok(())
}

View File

@ -1,10 +1,18 @@
<script lang="ts"> <script lang="ts">
import Router, {location} from "svelte-spa-router" import Router, {location} from "svelte-spa-router"
import { Header, Content, SideNav, SideNavItems, SideNavLink } from "carbon-components-svelte"; import { Header, Content, SideNav, SideNavItems, SideNavLink } from "carbon-components-svelte";
import User from "carbon-icons-svelte/lib/User.svelte";
import Home from './routes/Home.svelte' import Home from './routes/Home.svelte'
import FactoriesManagers from './routes/Factories-Managers.svelte' import FactoriesManagers from './routes/Factories-Managers.svelte'
import { factories, list_factories, list_managers, list_processess, managers, processess } from "./lib/api";
import { onMount } from "svelte";
import Processess from "./routes/Processess.svelte";
import { Building, Calibrate } from "carbon-icons-svelte";
onMount(async () => {
$managers = await list_managers()
$factories = await list_factories()
$processess = await list_processess()
})
let isSideNavOpen = false; let isSideNavOpen = false;
</script> </script>
@ -13,11 +21,17 @@
<SideNav bind:isOpen={isSideNavOpen} rail> <SideNav bind:isOpen={isSideNavOpen} rail>
<SideNavItems> <SideNavItems>
<SideNavLink <SideNavLink
icon={User} icon={Building}
text="Factories & Managers" text="Factories & Managers"
href="/#/factories-managers" href="/#/factories-managers"
isSelected={$location == "/factories-managers"} isSelected={$location == "/factories-managers"}
/> />
<SideNavLink
icon={Calibrate}
text="Processess"
href="/#/processess"
isSelected={$location == "/processess"}
/>
</SideNavItems> </SideNavItems>
</SideNav> </SideNav>
@ -25,5 +39,6 @@
<Router routes={{ <Router routes={{
"/": Home, "/": Home,
"/factories-managers": FactoriesManagers, "/factories-managers": FactoriesManagers,
"/processess": Processess
}} /> }} />
</Content> </Content>

View File

@ -0,0 +1,129 @@
<script lang="ts">
import {
DataTable,
Toolbar,
ToolbarContent,
ToolbarBatchActions,
Button,
ToolbarSearch,
Modal,
} from "carbon-components-svelte"
import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte"
import Edit from "carbon-icons-svelte/lib/Edit.svelte"
import { createEventDispatcher } from "svelte"
import type { DataTableHeader } from "carbon-components-svelte/types/DataTable/DataTable.svelte";
const dispatch = createEventDispatcher()
export let addModalTitle = "Create"
export let updateModalTitle = "Edit"
export let rows = []
export let headers: DataTableHeader[] = []
let isModalShown = false
let activeDelete = false
let selectedRowIds = []
let filteredRowIds = []
let currentlyUpdatingId = undefined
export let showUpdateModal = id => {
currentlyUpdatingId = id
dispatch("showUpdateModal", id)
isModalShown = true
}
let showAddModal = () => {
currentlyUpdatingId = undefined
dispatch("showAddModal")
isModalShown = true
}
let closeModal = () => {
isModalShown = false
}
let deleteSelectedRows = async () => {
dispatch("delete", selectedRowIds)
selectedRowIds = []
}
</script>
<DataTable
zebra
selectable={activeDelete}
batchSelection={activeDelete}
sortable
bind:selectedRowIds
headers={[
...headers,
{ key: "update_btn", empty: true, width: "5rem" }
]}
{rows}
>
<Toolbar>
<ToolbarBatchActions
bind:active={activeDelete}
on:cancel={(e) => {
e.preventDefault();
activeDelete = false;
}}
>
<Button
icon={TrashCan}
disabled={selectedRowIds.length === 0}
on:click={deleteSelectedRows}
>
Delete
</Button>
</ToolbarBatchActions>
<ToolbarContent>
<ToolbarSearch
persistent
shouldFilterRows
bind:filteredRowIds
/>
<Button on:click={showAddModal}>Create</Button>
<Button on:click={() => activeDelete = true}>Delete</Button>
</ToolbarContent>
</Toolbar>
<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}
</svelte:fragment>
</DataTable>
<Modal
bind:open={isModalShown}
modalHeading={currentlyUpdatingId != undefined ? updateModalTitle : addModalTitle}
primaryButtonText="Confirm"
secondaryButtonText="Cancel"
on:open
on:click:button--secondary={closeModal}
on:submit={async () => {
let valid = dispatch("validateModal", undefined, { cancelable: true })
if (!valid) {
return false
}
// TODO: Add erro handling
if (currentlyUpdatingId != undefined) {
dispatch("update", currentlyUpdatingId)
} else {
dispatch("add")
}
closeModal()
return true
}}
>
<slot></slot>
</Modal>

View File

@ -0,0 +1,47 @@
<script lang="ts">
import { Form, TextInput, NumberInput } from "carbon-components-svelte";
import type { ProcessData } from "./api"
export let process: ProcessData = {
name: "",
size: 1
}
// Name validation
let showNameInvalid = false;
function validateName() {
showNameInvalid = process.name === ""
}
// Exports
export const validate = () => {
validateName()
return !showNameInvalid && process.size >= 1
};
export const reset = () => {
process.name = ""
process.size = 1
showNameInvalid = false
};
</script>
<Form class="flex flex-col gap-1rem" title="Process">
<TextInput
bind:value={process.name}
invalidText={"Name can't be empty"}
bind:invalid={showNameInvalid}
on:input={validateName}
required
labelText="Name"
/>
<NumberInput
bind:value={process.size}
required
hideSteppers
min={1}
invalidText="Size must be at least 1m²"
label="Size (m²)"
/>
</Form>

View File

@ -30,8 +30,19 @@ export interface FactoryData {
floor_size: number floor_size: number
} }
export let factories = writable<Factory[]>([]) export interface Process {
export let managers = writable<Manager[]>([]) id: number,
name: string,
size: number
}
export interface ProcessData {
name: string,
size: number
}
export let factories = writable<Factory[]>([])
export let managers = writable<Manager[]>([])
export let processess = writable<Process[]>([])
function remove_manager_by_id(id: number) { function remove_manager_by_id(id: number) {
managers.update(managers => { managers.update(managers => {
@ -51,6 +62,10 @@ export async function list_managers(): Promise<Manager[]> {
return invoke("list_managers") return invoke("list_managers")
} }
export async function list_processess(): Promise<Process[]> {
return invoke("list_processess")
}
// Result -> Promise<[factory_id, manager_id]> // Result -> Promise<[factory_id, manager_id]>
export async function add_manager_factory(factory: FactoryData, manager: ManagerData): Promise<[number, number]> { export async function add_manager_factory(factory: FactoryData, manager: ManagerData): Promise<[number, number]> {
// TODO: For now always assume success // TODO: For now always assume success
@ -114,4 +129,48 @@ export async function delete_factory(id: number): Promise<void> {
} }
return factories return factories
}) })
}
export async function delete_process(id: number): Promise<void> {
invoke("delete_process", { id })
processess.update((processess) => {
var index = processess.findIndex(f => f.id == id)
if (index != -1) {
// TODO: Remove from associated factories
processess.splice(index, 1)
}
return processess
})
}
export async function add_process(process: ProcessData): Promise<number> {
// TODO: For now always assume success
// TODO: handle error
let id = await invoke<number>("add_process", { process })
processess.update((processess) => {
processess.push({
id,
...process
})
return processess
})
return id
}
export async function update_process(id: number, process: ProcessData): Promise<void> {
invoke("update_process", { id, process })
processess.update((processess) => {
var index = processess.findIndex(f => f.id == id)
if (index != -1) {
processess[index] = {
...processess[index],
...process
}
}
return processess
})
} }

View File

@ -6,26 +6,17 @@
ToolbarBatchActions, ToolbarBatchActions,
Button, Button,
ToolbarSearch, ToolbarSearch,
Pagination,
Modal, Modal,
} from "carbon-components-svelte" } from "carbon-components-svelte"
import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte" import TrashCan from "carbon-icons-svelte/lib/TrashCan.svelte"
import Edit from "carbon-icons-svelte/lib/Edit.svelte" import Edit from "carbon-icons-svelte/lib/Edit.svelte"
import ManagerForm from "../lib/ManagerForm.svelte" import ManagerForm from "../lib/ManagerForm.svelte"
import FactoryForm from "../lib/FactoryForm.svelte" import FactoryForm from "../lib/FactoryForm.svelte"
import { add_manager_factory, delete_factory, update_factory, update_manager, factories, list_factories, list_managers, managers } from "../lib/api" import { add_manager_factory, delete_factory, update_factory, update_manager, factories, managers } from "../lib/api"
import { onMount } from 'svelte'
var rows = [] var rows = []
onMount(async () => {
$managers = await list_managers()
$factories = await list_factories()
console.log($managers)
})
$: { $: {
console.log($factories)
rows = $factories.map(factory => { rows = $factories.map(factory => {
let manager = $managers.find(m => m.id == factory.manager_id) let manager = $managers.find(m => m.id == factory.manager_id)
return { return {

View File

@ -0,0 +1,46 @@
<script lang="ts">
import { add_process, delete_process, processess, update_process } from "../lib/api";
import MyDataTable from "../lib/MyDataTable.svelte";
import ProcessForm from "../lib/ProcessForm.svelte";
let resetProcessForm, validateProcessForm, processData
</script>
<MyDataTable
addModalTitle={"Create process"}
updateModalTitle={"Edit process"}
rows={$processess}
on:delete={async (event) => {
let ids = event.detail
for (var id of ids) {
await delete_process(id)
}
}}
on:showAddModal={resetProcessForm}
on:showUpdateModal={(event) => {
let id = event.detail
let process = $processess.find(process => process.id == id)
processData.name = process.name
processData.size = process.size
}}
on:validateModal={(event) => {
if (!validateProcessForm()) event.preventDefault()
}}
on:add={async () => {
await add_process(processData)
}}
on:update={async (event) => {
let id = event.detail
await update_process(id, processData)
}}
headers={[
{ key: "name", value: "Name" },
{ key: "size", value: "Size" }
]}
>
<ProcessForm
bind:process={processData}
bind:validate={validateProcessForm}
bind:reset={resetProcessForm}
/>
</MyDataTable>