This commit is contained in:
2023-08-29 21:33:59 +02:00
parent 859299f4ce
commit dab809be5c
7 changed files with 444 additions and 107 deletions

10
rust/Cargo.lock generated
View File

@@ -734,6 +734,7 @@ dependencies = [
"hyper", "hyper",
"maud", "maud",
"serde", "serde",
"serde_variant",
"sqlx", "sqlx",
"time", "time",
"tokio", "tokio",
@@ -1008,6 +1009,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_variant"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47a8ec0b2fd0506290348d9699c0e3eb2e3e8c0498b5a9a6158b3bd4d6970076"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.5" version = "0.10.5"

View File

@@ -51,3 +51,6 @@ features = ["serde"]
[dependencies.serde] [dependencies.serde]
version = "1.0.162" version = "1.0.162"
features = ["derive"] features = ["derive"]
[dependencies.serde_variant]
version = "0.1"

View File

@@ -181,8 +181,8 @@ impl InventoryItemList {
@if state.edit_item.map_or(false, |edit_item| edit_item == item.id) { @if state.edit_item.map_or(false, |edit_item| edit_item == item.id) {
tr ."h-10" { tr ."h-10" {
td ."border" ."bg-blue-300" ."px-2" ."py-0" { td ."border" ."bg-blue-300" ."px-2" ."py-0" {
div ."h-full" ."w-full" { div ."h-full" ."w-full" ."flex" {
input ."px-1" ."block" ."w-full" ."bg-blue-100" ."hover:bg-white" input ."m-auto" ."px-1" ."block" ."w-full" ."bg-blue-100" ."hover:bg-white"
type="text" type="text"
id="edit-item-name" id="edit-item-name"
name="edit-item-name" name="edit-item-name"
@@ -192,8 +192,8 @@ impl InventoryItemList {
} }
} }
td ."border" ."bg-blue-300" ."px-2" ."py-0" { td ."border" ."bg-blue-300" ."px-2" ."py-0" {
div ."h-full" ."w-full" { div ."h-full" ."w-full" ."flex" {
input ."px-1"."block" ."w-full" ."bg-blue-100" ."hover:bg-white" input ."m-auto" ."px-1"."block" ."w-full" ."bg-blue-100" ."hover:bg-white"
type="number" type="number"
id="edit-item-weight" id="edit-item-weight"
name="edit-item-weight" name="edit-item-weight"
@@ -202,18 +202,48 @@ impl InventoryItemList {
{} {}
} }
} }
td ."border-none" ."bg-green-100" ."hover:bg-green-200" .flex ."p-0" { td
div .aspect-square .w-full .h-full .flex { ."border-none"
button type="submit" form="edit-item" .m-auto .w-full .h-full { ."bg-green-100"
span ."mdi" ."mdi-content-save" ."text-xl" .m-auto {} ."hover:bg-green-200"
} ."p-0"
."h-full"
{
button
."aspect-square"
."flex"
."w-full"
."h-full"
type="submit"
form="edit-item"
{
span
."m-auto"
."mdi"
."mdi-content-save"
."text-xl";
} }
} }
td ."border-none" ."bg-red-100" ."hover:bg-red-200" ."p-0" { td
div .aspect-square .flex .w-full .h-full { ."border-none"
a href=(format!("/inventory/item/{id}/cancel", id = item.id)) .flex .m-auto .w-full .h-full { ."bg-red-100"
span ."mdi" ."mdi-cancel" ."text-xl" .m-auto {} ."hover:bg-red-200"
} ."p-0"
."h-full"
{
a
."aspect-square"
."flex"
."w-full"
."h-full"
."p-0"
href=(format!("/inventory/item/{id}/cancel", id = item.id))
{
span
."m-auto"
."mdi"
."mdi-cancel"
."text-xl";
} }
} }
} }
@@ -243,17 +273,16 @@ impl InventoryItemList {
."p-0" ."p-0"
."bg-blue-200" ."bg-blue-200"
."hover:bg-blue-400" ."hover:bg-blue-400"
."cursor-pointer"
."w-8" ."w-8"
."text-center" ."h-full"
{ {
div .aspect-square .flex .w-full .h-full { a
a href = (format!("?edit_item={id}", id = item.id)) ."m-auto" ."aspect-square"
{ ."flex"
button { ."w-full"
span ."mdi" ."mdi-pencil" ."text-xl" {} href=(format!("?edit_item={id}", id = item.id))
} {
} span ."m-auto" ."mdi" ."mdi-pencil" ."text-xl";
} }
} }
td td
@@ -261,18 +290,17 @@ impl InventoryItemList {
."p-0" ."p-0"
."bg-red-200" ."bg-red-200"
."hover:bg-red-400" ."hover:bg-red-400"
."cursor-pointer"
."w-8" ."w-8"
."text-center" ."h-full"
{
a
."aspect-square"
."flex"
."w-full"
href=(format!("/inventory/item/{id}/delete", id = item.id))
{ {
div .aspect-square .flex .w-full .h-full { span ."m-auto" ."mdi" ."mdi-delete" ."text-xl";
a href = (format!("/inventory/item/{id}/delete", id = item.id)) ."m-auto" }
{
button {
span ."mdi" ."mdi-delete" ."text-xl" {}
}
}
}
} }
} }
} }

View File

@@ -38,7 +38,6 @@ impl Root {
."flex-nowrap" ."flex-nowrap"
."justify-between" ."justify-between"
."items-center" ."items-center"
hx-boost="true"
{ {
span ."text-xl" ."font-semibold" { span ."text-xl" ."font-semibold" {
a href="/" { "Packager" } a href="/" { "Packager" }
@@ -54,7 +53,9 @@ impl Root {
}} { "Trips" } }} { "Trips" }
} }
} }
div hx-boost="true" { div
hx-boost="true"
{
(body) (body)
} }
} }
@@ -67,12 +68,17 @@ pub struct ErrorPage;
impl ErrorPage { impl ErrorPage {
pub fn build(message: &str) -> Markup { pub fn build(message: &str) -> Markup {
Root::build( html!(
html!( (DOCTYPE)
html {
head {
title { "Packager" }
}
body {
h1 { "Error" } h1 { "Error" }
p { (message) } p { (message) }
), }
&TopLevelPage::None, }
) )
} }
} }

View File

@@ -1,8 +1,12 @@
use crate::models; use crate::models;
use crate::models::*; use crate::models::*;
use maud::{html, Markup}; use maud::{html, Markup, PreEscaped};
use uuid::Uuid;
use serde_variant::to_variant_name;
use crate::ClientState;
pub struct TripManager; pub struct TripManager;
impl TripManager { impl TripManager {
@@ -16,6 +20,22 @@ impl TripManager {
} }
} }
pub enum InputType {
Text,
Number,
Date,
}
impl From<InputType> for &'static str {
fn from(value: InputType) -> &'static str {
match value {
InputType::Text => "text",
InputType::Number => "number",
InputType::Date => "date",
}
}
}
pub struct TripTable; pub struct TripTable;
impl TripTable { impl TripTable {
@@ -42,31 +62,11 @@ impl TripTable {
tbody { tbody {
@for trip in trips { @for trip in trips {
tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" { tr ."h-10" ."even:bg-gray-100" ."hover:bg-purple-100" ."h-full" {
td ."border" ."p-0" ."m-0" { (TripTableRow::build(trip.id, &trip.name))
a ."inline-block" ."p-2" ."m-0" ."w-full" (TripTableRow::build(trip.id, &trip.date_start))
href=(format!("/trip/{id}/", id=trip.id)) (TripTableRow::build(trip.id, &trip.date_end))
{ (trip.name) } (TripTableRow::build(trip.id, (trip.date_end - trip.date_start).whole_days()))
} (TripTableRow::build(trip.id, trip.state))
td ."border" ."p-0" ."m-0" {
a ."inline-block" ."p-2" ."m-0" ."w-full"
href=(format!("/trip/{id}/", id=trip.id))
{ (trip.start_date) }
}
td ."border" ."p-0" ."m-0" {
a ."inline-block" ."p-2" ."m-0" ."w-full"
href=(format!("/trip/{id}/", id=trip.id))
{ (trip.end_date) }
}
td ."border" ."p-0" ."m-0" {
a ."inline-block" ."p-2" ."m-0" ."w-full"
href=(format!("/trip/{id}/", id=trip.id))
{ ((trip.end_date - trip.start_date).whole_days()) }
}
td ."border" ."p-0" ."m-0" {
a ."inline-block" ."p-2" ."m-0" ."w-full"
href=(format!("/trip/{id}/", id=trip.id))
{ (trip.state.to_string()) }
}
} }
} }
} }
@@ -75,6 +75,20 @@ impl TripTable {
} }
} }
pub struct TripTableRow;
impl TripTableRow {
pub fn build(trip_id: Uuid, value: impl std::fmt::Display) -> Markup {
html!(
td ."border" ."p-0" ."m-0" {
a ."inline-block" ."p-2" ."m-0" ."w-full"
href=(format!("/trip/{id}/", id=trip_id))
{ (value) }
}
)
}
}
pub struct NewTrip; pub struct NewTrip;
impl NewTrip { impl NewTrip {
@@ -177,14 +191,129 @@ impl NewTrip {
pub struct Trip; pub struct Trip;
impl Trip { impl Trip {
pub fn build(trip: &models::Trip) -> Markup { pub fn build(state: &ClientState, trip: &models::Trip) -> Markup {
html!( html!(
div ."p-8" { div ."p-8" {
div ."flex" ."flex-row" ."items-center" ."gap-x-3" { div ."flex" ."flex-row" ."items-center" ."gap-x-3" {
h1 ."text-2xl" ."font-semibold"{ (trip.name) } h1 ."text-2xl" ."font-semibold"{ (trip.name) }
} }
div ."my-6" { div ."my-6" {
(TripInfo::build(&trip)) (TripInfo::build(state, &trip))
}
div ."my-6" {
(TripComment::build(&trip))
}
}
)
}
}
pub struct TripInfoRow;
impl TripInfoRow {
pub fn build(
name: &str,
value: impl std::fmt::Display,
attribute_key: TripAttribute,
edit_attribute: Option<&TripAttribute>,
input_type: InputType,
) -> Markup {
let edit = edit_attribute.map_or(false, |a| *a == attribute_key);
html!(
@if edit {
form
name="edit-trip"
id="edit-trip"
action=(format!("edit/{key}/submit", key=(to_variant_name(&attribute_key).unwrap()) ))
// hx-post=(format!("edit/{name}/submit"))
target="."
method="post"
;
}
tr .h-full {
@if edit {
td ."border" ."p-2" { (name) }
td ."border" ."bg-blue-300" ."px-2" ."py-0" {
div ."h-full" ."w-full" ."flex" {
input ."m-auto" ."px-1" ."block" ."w-full" ."bg-blue-100" ."hover:bg-white"
type=(<InputType as Into<&'static str>>::into(input_type))
id="new-value"
name="new-value"
form="edit-trip"
value=(value)
;
}
}
td
."border-none"
."bg-red-100"
."hover:bg-red-200"
."p-0"
."h-full"
."w-8"
{
a
."aspect-square"
."flex"
."w-full"
."h-full"
."p-0"
href="." // strips query parameters
{
span
."m-auto"
."mdi"
."mdi-cancel"
."text-xl";
}
}
td
."border-none"
."bg-green-100"
."hover:bg-green-200"
."p-0"
."h-full"
."w-8"
{
button
."aspect-square"
."flex"
."w-full"
."h-full"
type="submit"
form="edit-trip"
{
span
."m-auto"
."mdi"
."mdi-content-save"
."text-xl";
}
}
} @else {
td ."border" ."p-2" { (name) }
td ."border" ."p-2" { (value) }
td
."border-none"
."bg-blue-100"
."hover:bg-blue-200"
."p-0"
."w-8"
."h-full"
{
a
.flex
."w-full"
."h-full"
href={ "?edit=" (to_variant_name(&attribute_key).unwrap()) }
{
span
."m-auto"
."mdi"
."mdi-pencil"
."text-xl";
}
}
} }
} }
) )
@@ -194,7 +323,7 @@ impl Trip {
pub struct TripInfo; pub struct TripInfo;
impl TripInfo { impl TripInfo {
pub fn build(trip: &models::Trip) -> Markup { pub fn build(state: &ClientState, trip: &models::Trip) -> Markup {
html!( html!(
table table
."table" ."table"
@@ -205,31 +334,12 @@ impl TripInfo {
."w-full" ."w-full"
{ {
tbody { tbody {
tr { (TripInfoRow::build("Location", &trip.location, TripAttribute::Location, state.trip_edit_attribute.as_ref(), InputType::Text))
td ."border" ."p-2" { "State" } (TripInfoRow::build("Start date", trip.date_start, TripAttribute::DateStart, state.trip_edit_attribute.as_ref(), InputType::Date))
td ."border" ."p-2" { (trip.state.to_string()) } (TripInfoRow::build("End date", trip.date_end, TripAttribute::DateEnd, state.trip_edit_attribute.as_ref(), InputType::Date))
} (TripInfoRow::build("Temp (min)", trip.temp_min, TripAttribute::TempMin, state.trip_edit_attribute.as_ref(), InputType::Number))
tr { (TripInfoRow::build("Temp (max)", trip.temp_max, TripAttribute::TempMax, state.trip_edit_attribute.as_ref(), InputType::Number))
td ."border" ."p-2" { "Location" } tr .h-full {
td ."border" ."p-2" { (trip.location) }
}
tr {
td ."border" ."p-2" { "Start date" }
td ."border" ."p-2" { (trip.start_date) }
}
tr {
td ."border" ."p-2" { "End date" }
td ."border" ."p-2" { (trip.end_date) }
}
tr {
td ."border" ."p-2" { "Temp (min)" }
td ."border" ."p-2" { (trip.temp_min) }
}
tr {
td ."border" ."p-2" { "Temp (max)" }
td ."border" ."p-2" { (trip.temp_max) }
}
tr {
td ."border" ."p-2" { "Types" } td ."border" ."p-2" { "Types" }
td ."border" ."p-2" { td ."border" ."p-2" {
ul ul
@@ -305,3 +415,49 @@ impl TripInfo {
) )
} }
} }
pub struct TripComment;
impl TripComment {
pub fn build(trip: &models::Trip) -> Markup {
html!(
h1 ."text-xl" ."mb-5" { "Comments" }
form
id="edit-comment"
action="comment/submit"
target="_self"
method="post"
;
// https://stackoverflow.com/a/48460773
textarea
#"comment"
."border" ."w-full" ."h-48"
name="new-comment"
form="edit-comment"
autocomplete="off"
oninput=r#"this.style.height = "";this.style.height = this.scrollHeight + 2 + "px""#
{ (trip.comment.as_ref().unwrap_or(&"".to_string())) }
script defer { (PreEscaped(r#"e = document.getElementById("comment"); e.style.height = e.scrollHeight + 2 + "px";"#)) }
button
type="submit"
form="edit-comment"
."mt-2"
."border"
."bg-green-200"
."hover:bg-green-400"
."cursor-pointer"
."flex"
."flex-column"
."p-2"
."gap-2"
."items-center"
{
span ."mdi" ."mdi-content-save" ."text-xl";
span { "Save" }
}
)
}
}

View File

@@ -9,6 +9,8 @@ use axum::{
Form, Router, Form, Router,
}; };
use serde_variant::to_variant_name;
use sqlx::{ use sqlx::{
error::DatabaseError, error::DatabaseError,
query, query,
@@ -42,6 +44,7 @@ pub struct AppState {
pub struct ClientState { pub struct ClientState {
pub active_category_id: Option<Uuid>, pub active_category_id: Option<Uuid>,
pub edit_item: Option<Uuid>, pub edit_item: Option<Uuid>,
pub trip_edit_attribute: Option<TripAttribute>,
} }
impl ClientState { impl ClientState {
@@ -49,6 +52,7 @@ impl ClientState {
ClientState { ClientState {
active_category_id: None, active_category_id: None,
edit_item: None, edit_item: None,
trip_edit_attribute: None,
} }
} }
} }
@@ -86,8 +90,13 @@ async fn main() -> Result<(), sqlx::Error> {
.route("/trips/", get(trips)) .route("/trips/", get(trips))
.route("/trip/", post(trip_create)) .route("/trip/", post(trip_create))
.route("/trip/:id/", get(trip)) .route("/trip/:id/", get(trip))
.route("/trip/:id/comment/submit", post(trip_comment_set))
.route("/trip/:id/type/:id/add", get(trip_type_add)) .route("/trip/:id/type/:id/add", get(trip_type_add))
.route("/trip/:id/type/:id/remove", get(trip_type_remove)) .route("/trip/:id/type/:id/remove", get(trip_type_remove))
.route(
"/trip/:id/edit/:attribute/submit",
post(trip_edit_attribute),
)
.route("/inventory/", get(inventory_inactive)) .route("/inventory/", get(inventory_inactive))
.route("/inventory/item/", post(inventory_item_create)) .route("/inventory/item/", post(inventory_item_create))
.route("/inventory/category/:id/", get(inventory_active)) .route("/inventory/category/:id/", get(inventory_active))
@@ -408,9 +417,9 @@ struct NewTrip {
#[serde(rename = "new-trip-name")] #[serde(rename = "new-trip-name")]
name: String, name: String,
#[serde(rename = "new-trip-start-date")] #[serde(rename = "new-trip-start-date")]
start_date: time::Date, date_start: time::Date,
#[serde(rename = "new-trip-end-date")] #[serde(rename = "new-trip-end-date")]
end_date: time::Date, date_end: time::Date,
} }
async fn trip_create( async fn trip_create(
@@ -420,14 +429,14 @@ async fn trip_create(
let id = Uuid::new_v4(); let id = Uuid::new_v4();
query( query(
"INSERT INTO trips "INSERT INTO trips
(id, name, start_date, end_date) (id, name, date_start, date_end)
VALUES VALUES
(?, ?, ?, ?)", (?, ?, ?, ?)",
) )
.bind(id.to_string()) .bind(id.to_string())
.bind(&new_trip.name) .bind(&new_trip.name)
.bind(new_trip.start_date) .bind(new_trip.date_start)
.bind(new_trip.end_date) .bind(new_trip.date_end)
.execute(&state.database_pool) .execute(&state.database_pool)
.await .await
.map_err(|e| match e { .map_err(|e| match e {
@@ -499,12 +508,20 @@ async fn trips(
)) ))
} }
#[derive(Debug, Deserialize)]
struct TripQuery {
edit: Option<TripAttribute>,
}
async fn trip( async fn trip(
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
State(state): State<AppState>, State(mut state): State<AppState>,
Query(trip_query): Query<TripQuery>,
) -> Result<(StatusCode, Markup), (StatusCode, Markup)> { ) -> Result<(StatusCode, Markup), (StatusCode, Markup)> {
state.client_state.trip_edit_attribute = trip_query.edit;
let mut trip: models::Trip = let mut trip: models::Trip =
query("SELECT id,name,start_date,end_date,state,location,temp_min,temp_max FROM trips WHERE id = ?") query("SELECT id,name,date_start,date_end,state,location,temp_min,temp_max,comment FROM trips WHERE id = ?")
.bind(id.to_string()) .bind(id.to_string())
.fetch_one(&state.database_pool) .fetch_one(&state.database_pool)
.map_ok(std::convert::TryInto::try_into) .map_ok(std::convert::TryInto::try_into)
@@ -529,7 +546,10 @@ async fn trip(
Ok(( Ok((
StatusCode::OK, StatusCode::OK,
Root::build(components::Trip::build(&trip), &TopLevelPage::Trips), Root::build(
components::Trip::build(&state.client_state, &trip),
&TopLevelPage::Trips,
),
)) ))
} }
@@ -621,3 +641,68 @@ async fn trip_type_add(
Ok(Redirect::to(&format!("/trip/{trip_id}/"))) Ok(Redirect::to(&format!("/trip/{trip_id}/")))
} }
#[derive(Deserialize)]
struct CommentUpdate {
#[serde(rename = "new-comment")]
new_comment: String,
}
async fn trip_comment_set(
Path(trip_id): Path<Uuid>,
State(state): State<AppState>,
Form(comment_update): Form<CommentUpdate>,
) -> Result<Redirect, (StatusCode, Markup)> {
let result = query(
"UPDATE trips
SET comment = ?
WHERE id = ?",
)
.bind(comment_update.new_comment)
.bind(trip_id.to_string())
.execute(&state.database_pool)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
if result.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("trip with id {id} not found", id = trip_id)),
))
} else {
Ok(Redirect::to(&format!("/trip/{id}/", id = trip_id)))
}
}
#[derive(Deserialize)]
struct TripUpdate {
#[serde(rename = "new-value")]
new_value: String,
}
async fn trip_edit_attribute(
Path((trip_id, attribute)): Path<(Uuid, TripAttribute)>,
State(state): State<AppState>,
Form(trip_update): Form<TripUpdate>,
) -> Result<Redirect, (StatusCode, Markup)> {
let result = query(&format!(
"UPDATE trips
SET {attribute} = ?
WHERE id = ?",
attribute = to_variant_name(&attribute).unwrap()
))
.bind(trip_update.new_value)
.bind(trip_id.to_string())
.execute(&state.database_pool)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, ErrorPage::build(&e.to_string())))?;
if result.rows_affected() == 0 {
Err((
StatusCode::NOT_FOUND,
ErrorPage::build(&format!("trip with id {id} not found", id = trip_id)),
))
} else {
Ok(Redirect::to(&format!("/trips/")))
}
}

View File

@@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use sqlx::{ use sqlx::{
database::Database, database::Database,
database::HasValueRef, database::HasValueRef,
@@ -90,39 +91,87 @@ impl fmt::Display for TripState {
pub struct Trip { pub struct Trip {
pub id: Uuid, pub id: Uuid,
pub name: String, pub name: String,
pub start_date: time::Date, pub date_start: time::Date,
pub end_date: time::Date, pub date_end: time::Date,
pub state: TripState, pub state: TripState,
pub location: String, pub location: String,
pub temp_min: i32, pub temp_min: i32,
pub temp_max: i32, pub temp_max: i32,
pub comment: Option<String>,
types: Option<Vec<TripType>>, types: Option<Vec<TripType>>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum TripAttribute {
#[serde(rename = "date_start")]
DateStart,
#[serde(rename = "date_end")]
DateEnd,
#[serde(rename = "location")]
Location,
#[serde(rename = "temp_min")]
TempMin,
#[serde(rename = "temp_max")]
TempMax,
}
// impl std::convert::Into<&'static str> for TripAttribute {
// fn into(self) -> &'static str {
// match self {
// Self::DateStart => "date_start",
// Self::DateEnd => "date_end",
// Self::Location => "location",
// Self::TempMin => "temp_min",
// Self::TempMax => "temp_max",
// }
// }
// }
// impl std::convert::TryFrom<&str> for TripAttribute {
// type Error = Error;
// fn try_from(value: &str) -> Result<Self, Error> {
// Ok(match value {
// "date_start" => Self::DateStart,
// "date_end" => Self::DateEnd,
// "location" => Self::Location,
// "temp_min" => Self::TempMin,
// "temp_max" => Self::TempMax,
// _ => {
// return Err(Error::UnknownAttributeValue {
// attribute: value.to_string(),
// })
// }
// })
// }
// }
impl TryFrom<SqliteRow> for Trip { impl TryFrom<SqliteRow> for Trip {
type Error = Error; type Error = Error;
fn try_from(row: SqliteRow) -> Result<Self, Self::Error> { fn try_from(row: SqliteRow) -> Result<Self, Self::Error> {
let name: &str = row.try_get("name")?; let name: &str = row.try_get("name")?;
let id: &str = row.try_get("id")?; let id: &str = row.try_get("id")?;
let start_date: time::Date = row.try_get("start_date")?; let date_start: time::Date = row.try_get("date_start")?;
let end_date: time::Date = row.try_get("end_date")?; let date_end: time::Date = row.try_get("date_end")?;
let state: TripState = row.try_get("state")?; let state: TripState = row.try_get("state")?;
let location = row.try_get("location")?; let location = row.try_get("location")?;
let temp_min = row.try_get("temp_min")?; let temp_min = row.try_get("temp_min")?;
let temp_max = row.try_get("temp_max")?; let temp_max = row.try_get("temp_max")?;
let comment = row.try_get("comment")?;
let id: Uuid = Uuid::try_parse(id)?; let id: Uuid = Uuid::try_parse(id)?;
Ok(Trip { Ok(Trip {
id, id,
name: name.to_string(), name: name.to_string(),
start_date, date_start,
end_date, date_end,
state, state,
location, location,
temp_min, temp_min,
temp_max, temp_max,
comment,
types: None, types: None,
}) })
} }