mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
server: Merge insert and update methods and routes
This commit is contained in:
parent
319b1505da
commit
3b8ed4bdb1
11 changed files with 611 additions and 662 deletions
|
|
@ -1,75 +1,99 @@
|
||||||
use super::schema::ensembles;
|
use super::schema::ensembles;
|
||||||
use super::DbConn;
|
use super::{DbConn, User};
|
||||||
use anyhow::Result;
|
use crate::error::ServerError;
|
||||||
|
use anyhow::{Error, Result};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::{Insertable, Queryable};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// An ensemble that takes part in recordings.
|
/// A ensemble as represented within the API.
|
||||||
#[derive(Insertable, Queryable, Serialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Ensemble {
|
pub struct Ensemble {
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A ensemble as represented in the database.
|
||||||
|
#[derive(Insertable, Queryable, AsChangeset, Debug, Clone)]
|
||||||
|
#[table_name = "ensembles"]
|
||||||
|
struct EnsembleRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
pub created_by: String,
|
pub created_by: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure representing data on an ensemble.
|
impl From<EnsembleRow> for Ensemble {
|
||||||
#[derive(AsChangeset, Deserialize, Debug, Clone)]
|
fn from(row: EnsembleRow) -> Ensemble {
|
||||||
#[table_name = "ensembles"]
|
Ensemble {
|
||||||
#[serde(rename_all = "camelCase")]
|
id: row.id as u32,
|
||||||
pub struct EnsembleInsertion {
|
name: row.name,
|
||||||
pub name: String,
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new ensemble.
|
/// Update an existing ensemble or insert a new one. This will only work, if the provided user is
|
||||||
pub fn insert_ensemble(
|
/// allowed to do that.
|
||||||
conn: &DbConn,
|
pub fn update_ensemble(conn: &DbConn, ensemble: &Ensemble, user: &User) -> Result<()> {
|
||||||
id: u32,
|
let old_row = get_ensemble_row(conn, ensemble.id)?;
|
||||||
data: &EnsembleInsertion,
|
|
||||||
created_by: &str,
|
let allowed = match old_row {
|
||||||
) -> Result<()> {
|
Some(row) => user.may_edit(&row.created_by),
|
||||||
let ensemble = Ensemble {
|
None => user.may_create(),
|
||||||
id: id as i64,
|
};
|
||||||
name: data.name.clone(),
|
|
||||||
created_by: created_by.to_string(),
|
if allowed {
|
||||||
|
let new_row = EnsembleRow {
|
||||||
|
id: ensemble.id as i64,
|
||||||
|
name: ensemble.name.clone(),
|
||||||
|
created_by: user.username.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(ensembles::table)
|
diesel::insert_into(ensembles::table)
|
||||||
.values(ensemble)
|
.values(&new_row)
|
||||||
|
.on_conflict(ensembles::id)
|
||||||
|
.do_update()
|
||||||
|
.set(&new_row)
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing ensemble.
|
|
||||||
pub fn update_ensemble(conn: &DbConn, id: u32, data: &EnsembleInsertion) -> Result<()> {
|
|
||||||
diesel::update(ensembles::table)
|
|
||||||
.filter(ensembles::id.eq(id as i64))
|
|
||||||
.set(data)
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an existing ensemble.
|
/// Get an existing ensemble.
|
||||||
pub fn get_ensemble(conn: &DbConn, id: u32) -> Result<Option<Ensemble>> {
|
pub fn get_ensemble(conn: &DbConn, id: u32) -> Result<Option<Ensemble>> {
|
||||||
Ok(ensembles::table
|
let row = get_ensemble_row(conn, id)?;
|
||||||
.filter(ensembles::id.eq(id as i64))
|
let ensemble = row.map(|row| row.into());
|
||||||
.load::<Ensemble>(conn)?
|
|
||||||
.first()
|
Ok(ensemble)
|
||||||
.cloned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete an existing ensemble.
|
/// Delete an existing ensemble. This will only work if the provided user is allowed to do that.
|
||||||
pub fn delete_ensemble(conn: &DbConn, id: u32) -> Result<()> {
|
pub fn delete_ensemble(conn: &DbConn, id: u32, user: &User) -> Result<()> {
|
||||||
|
if user.may_delete() {
|
||||||
diesel::delete(ensembles::table.filter(ensembles::id.eq(id as i64))).execute(conn)?;
|
diesel::delete(ensembles::table.filter(ensembles::id.eq(id as i64))).execute(conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all existing ensembles.
|
/// Get all existing ensembles.
|
||||||
pub fn get_ensembles(conn: &DbConn) -> Result<Vec<Ensemble>> {
|
pub fn get_ensembles(conn: &DbConn) -> Result<Vec<Ensemble>> {
|
||||||
Ok(ensembles::table.load::<Ensemble>(conn)?)
|
let rows = ensembles::table.load::<EnsembleRow>(conn)?;
|
||||||
|
let ensembles: Vec<Ensemble> = rows.into_iter().map(|row| row.into()).collect();
|
||||||
|
|
||||||
|
Ok(ensembles)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a ensemble row if it exists.
|
||||||
|
fn get_ensemble_row(conn: &DbConn, id: u32) -> Result<Option<EnsembleRow>> {
|
||||||
|
let row = ensembles::table
|
||||||
|
.filter(ensembles::id.eq(id as i64))
|
||||||
|
.load::<EnsembleRow>(conn)?
|
||||||
|
.into_iter()
|
||||||
|
.next();
|
||||||
|
|
||||||
|
Ok(row)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,99 @@
|
||||||
use super::schema::instruments;
|
use super::schema::instruments;
|
||||||
use super::DbConn;
|
use super::{DbConn, User};
|
||||||
use anyhow::Result;
|
use crate::error::ServerError;
|
||||||
|
use anyhow::{Error, Result};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::{Insertable, Queryable};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// An instrument or any other possible role within a recording.
|
/// A instrument as represented within the API.
|
||||||
#[derive(Insertable, Queryable, Serialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Instrument {
|
pub struct Instrument {
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A instrument as represented in the database.
|
||||||
|
#[derive(Insertable, Queryable, AsChangeset, Debug, Clone)]
|
||||||
|
#[table_name = "instruments"]
|
||||||
|
struct InstrumentRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
pub created_by: String,
|
pub created_by: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure representing data on an instrument.
|
impl From<InstrumentRow> for Instrument {
|
||||||
#[derive(AsChangeset, Deserialize, Debug, Clone)]
|
fn from(row: InstrumentRow) -> Instrument {
|
||||||
#[table_name = "instruments"]
|
Instrument {
|
||||||
#[serde(rename_all = "camelCase")]
|
id: row.id as u32,
|
||||||
pub struct InstrumentInsertion {
|
name: row.name,
|
||||||
pub name: String,
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new instrument.
|
/// Update an existing instrument or insert a new one. This will only work, if the provided user is
|
||||||
pub fn insert_instrument(
|
/// allowed to do that.
|
||||||
conn: &DbConn,
|
pub fn update_instrument(conn: &DbConn, instrument: &Instrument, user: &User) -> Result<()> {
|
||||||
id: u32,
|
let old_row = get_instrument_row(conn, instrument.id)?;
|
||||||
data: &InstrumentInsertion,
|
|
||||||
created_by: &str,
|
let allowed = match old_row {
|
||||||
) -> Result<()> {
|
Some(row) => user.may_edit(&row.created_by),
|
||||||
let instrument = Instrument {
|
None => user.may_create(),
|
||||||
id: id as i64,
|
};
|
||||||
name: data.name.clone(),
|
|
||||||
created_by: created_by.to_string(),
|
if allowed {
|
||||||
|
let new_row = InstrumentRow {
|
||||||
|
id: instrument.id as i64,
|
||||||
|
name: instrument.name.clone(),
|
||||||
|
created_by: user.username.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(instruments::table)
|
diesel::insert_into(instruments::table)
|
||||||
.values(instrument)
|
.values(&new_row)
|
||||||
|
.on_conflict(instruments::id)
|
||||||
|
.do_update()
|
||||||
|
.set(&new_row)
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing instrument.
|
|
||||||
pub fn update_instrument(conn: &DbConn, id: u32, data: &InstrumentInsertion) -> Result<()> {
|
|
||||||
diesel::update(instruments::table)
|
|
||||||
.filter(instruments::id.eq(id as i64))
|
|
||||||
.set(data)
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an existing instrument.
|
/// Get an existing instrument.
|
||||||
pub fn get_instrument(conn: &DbConn, id: u32) -> Result<Option<Instrument>> {
|
pub fn get_instrument(conn: &DbConn, id: u32) -> Result<Option<Instrument>> {
|
||||||
Ok(instruments::table
|
let row = get_instrument_row(conn, id)?;
|
||||||
.filter(instruments::id.eq(id as i64))
|
let instrument = row.map(|row| row.into());
|
||||||
.load::<Instrument>(conn)?
|
|
||||||
.first()
|
Ok(instrument)
|
||||||
.cloned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete an existing instrument.
|
/// Delete an existing instrument. This will only work if the provided user is allowed to do that.
|
||||||
pub fn delete_instrument(conn: &DbConn, id: u32) -> Result<()> {
|
pub fn delete_instrument(conn: &DbConn, id: u32, user: &User) -> Result<()> {
|
||||||
|
if user.may_delete() {
|
||||||
diesel::delete(instruments::table.filter(instruments::id.eq(id as i64))).execute(conn)?;
|
diesel::delete(instruments::table.filter(instruments::id.eq(id as i64))).execute(conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all existing instruments.
|
/// Get all existing instruments.
|
||||||
pub fn get_instruments(conn: &DbConn) -> Result<Vec<Instrument>> {
|
pub fn get_instruments(conn: &DbConn) -> Result<Vec<Instrument>> {
|
||||||
Ok(instruments::table.load::<Instrument>(conn)?)
|
let rows = instruments::table.load::<InstrumentRow>(conn)?;
|
||||||
|
let instruments: Vec<Instrument> = rows.into_iter().map(|row| row.into()).collect();
|
||||||
|
|
||||||
|
Ok(instruments)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a instrument row if it exists.
|
||||||
|
fn get_instrument_row(conn: &DbConn, id: u32) -> Result<Option<InstrumentRow>> {
|
||||||
|
let row = instruments::table
|
||||||
|
.filter(instruments::id.eq(id as i64))
|
||||||
|
.load::<InstrumentRow>(conn)?
|
||||||
|
.into_iter()
|
||||||
|
.next();
|
||||||
|
|
||||||
|
Ok(row)
|
||||||
}
|
}
|
||||||
|
|
@ -1,78 +1,103 @@
|
||||||
use super::schema::persons;
|
use super::schema::persons;
|
||||||
use super::DbConn;
|
use super::{DbConn, User};
|
||||||
use anyhow::Result;
|
use crate::error::ServerError;
|
||||||
|
use anyhow::{Error, Result};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::{Insertable, Queryable};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// A person that is a composer, an interpret or both.
|
/// A person as represented within the API.
|
||||||
#[derive(Insertable, Queryable, Serialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Person {
|
pub struct Person {
|
||||||
|
pub id: u32,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A person as represented in the database.
|
||||||
|
#[derive(Insertable, Queryable, AsChangeset, Debug, Clone)]
|
||||||
|
#[table_name = "persons"]
|
||||||
|
struct PersonRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub first_name: String,
|
pub first_name: String,
|
||||||
pub last_name: String,
|
pub last_name: String,
|
||||||
|
|
||||||
#[serde(skip)]
|
|
||||||
pub created_by: String,
|
pub created_by: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure representing data on a person.
|
impl From<PersonRow> for Person {
|
||||||
#[derive(AsChangeset, Deserialize, Debug, Clone)]
|
fn from(row: PersonRow) -> Person {
|
||||||
#[table_name = "persons"]
|
Person {
|
||||||
#[serde(rename_all = "camelCase")]
|
id: row.id as u32,
|
||||||
pub struct PersonInsertion {
|
first_name: row.first_name,
|
||||||
pub first_name: String,
|
last_name: row.last_name,
|
||||||
pub last_name: String,
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new person.
|
/// Update an existing person or insert a new one. This will only work, if the provided user is
|
||||||
pub fn insert_person(
|
/// allowed to do that.
|
||||||
conn: &DbConn,
|
pub fn update_person(conn: &DbConn, person: &Person, user: &User) -> Result<()> {
|
||||||
id: u32,
|
let old_row = get_person_row(conn, person.id)?;
|
||||||
data: &PersonInsertion,
|
|
||||||
created_by: &str,
|
let allowed = match old_row {
|
||||||
) -> Result<()> {
|
Some(row) => user.may_edit(&row.created_by),
|
||||||
let person = Person {
|
None => user.may_create(),
|
||||||
id: id as i64,
|
};
|
||||||
first_name: data.first_name.clone(),
|
|
||||||
last_name: data.last_name.clone(),
|
if allowed {
|
||||||
created_by: created_by.to_string(),
|
let new_row = PersonRow {
|
||||||
|
id: person.id as i64,
|
||||||
|
first_name: person.first_name.clone(),
|
||||||
|
last_name: person.last_name.clone(),
|
||||||
|
created_by: user.username.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(persons::table)
|
diesel::insert_into(persons::table)
|
||||||
.values(person)
|
.values(&new_row)
|
||||||
|
.on_conflict(persons::id)
|
||||||
|
.do_update()
|
||||||
|
.set(&new_row)
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update an existing person.
|
|
||||||
pub fn update_person(conn: &DbConn, id: u32, data: &PersonInsertion) -> Result<()> {
|
|
||||||
diesel::update(persons::table)
|
|
||||||
.filter(persons::id.eq(id as i64))
|
|
||||||
.set(data)
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an existing person.
|
/// Get an existing person.
|
||||||
pub fn get_person(conn: &DbConn, id: u32) -> Result<Option<Person>> {
|
pub fn get_person(conn: &DbConn, id: u32) -> Result<Option<Person>> {
|
||||||
Ok(persons::table
|
let row = get_person_row(conn, id)?;
|
||||||
.filter(persons::id.eq(id as i64))
|
let person = row.map(|row| row.into());
|
||||||
.load::<Person>(conn)?
|
|
||||||
.first()
|
Ok(person)
|
||||||
.cloned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete an existing person.
|
/// Delete an existing person. This will only work if the provided user is allowed to do that.
|
||||||
pub fn delete_person(conn: &DbConn, id: u32) -> Result<()> {
|
pub fn delete_person(conn: &DbConn, id: u32, user: &User) -> Result<()> {
|
||||||
|
if user.may_delete() {
|
||||||
diesel::delete(persons::table.filter(persons::id.eq(id as i64))).execute(conn)?;
|
diesel::delete(persons::table.filter(persons::id.eq(id as i64))).execute(conn)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all existing persons.
|
/// Get all existing persons.
|
||||||
pub fn get_persons(conn: &DbConn) -> Result<Vec<Person>> {
|
pub fn get_persons(conn: &DbConn) -> Result<Vec<Person>> {
|
||||||
Ok(persons::table.load::<Person>(conn)?)
|
let rows = persons::table.load::<PersonRow>(conn)?;
|
||||||
|
let persons: Vec<Person> = rows.into_iter().map(|row| row.into()).collect();
|
||||||
|
|
||||||
|
Ok(persons)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a person row if it exists.
|
||||||
|
fn get_person_row(conn: &DbConn, id: u32) -> Result<Option<PersonRow>> {
|
||||||
|
let row = persons::table
|
||||||
|
.filter(persons::id.eq(id as i64))
|
||||||
|
.load::<PersonRow>(conn)?
|
||||||
|
.into_iter()
|
||||||
|
.next();
|
||||||
|
|
||||||
|
Ok(row)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,44 @@
|
||||||
use super::schema::{ensembles, instruments, performances, persons, recordings};
|
use super::schema::{ensembles, performances, persons, recordings};
|
||||||
use super::{get_work_description, DbConn, Ensemble, Instrument, Person, WorkDescription};
|
use super::{get_ensemble, get_instrument, get_person, get_work};
|
||||||
|
use super::{DbConn, Ensemble, Instrument, Person, User, Work};
|
||||||
|
use crate::error::ServerError;
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::{Insertable, Queryable};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
/// A specific recording of a work.
|
/// A specific recording of a work.
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Recording {
|
pub struct Recording {
|
||||||
|
pub id: u32,
|
||||||
|
pub work: Work,
|
||||||
|
pub comment: String,
|
||||||
|
pub performances: Vec<Performance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How a person or ensemble was involved in a recording.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Performance {
|
||||||
|
pub person: Option<Person>,
|
||||||
|
pub ensemble: Option<Ensemble>,
|
||||||
|
pub role: Option<Instrument>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Row data for a recording.
|
||||||
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
|
#[table_name = "recordings"]
|
||||||
|
struct RecordingRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub work: i64,
|
pub work: i64,
|
||||||
pub comment: String,
|
pub comment: String,
|
||||||
pub created_by: String,
|
pub created_by: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How a person or ensemble was involved in a recording.
|
/// Row data for a performance.
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
pub struct Performance {
|
#[table_name = "performances"]
|
||||||
|
struct PerformanceRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub recording: i64,
|
pub recording: i64,
|
||||||
pub person: Option<i64>,
|
pub person: Option<i64>,
|
||||||
|
|
@ -25,254 +46,189 @@ pub struct Performance {
|
||||||
pub role: Option<i64>,
|
pub role: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure for collecting all available information on a performance.
|
/// Update an existing recording or insert a new one. This will only work, if the provided user is
|
||||||
#[derive(Serialize, Debug, Clone)]
|
/// allowed to do that.
|
||||||
#[serde(rename_all = "camelCase")]
|
// TODO: Also add newly created associated items.
|
||||||
pub struct PerformanceDescription {
|
pub fn update_recording(conn: &DbConn, recording: &Recording, user: &User) -> Result<()> {
|
||||||
pub person: Option<Person>,
|
|
||||||
pub ensemble: Option<Ensemble>,
|
|
||||||
pub role: Option<Instrument>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure for collecting all available information on a recording.
|
|
||||||
#[derive(Serialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RecordingDescription {
|
|
||||||
pub id: i64,
|
|
||||||
pub work: WorkDescription,
|
|
||||||
pub comment: String,
|
|
||||||
pub performances: Vec<PerformanceDescription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure representing data on a performance.
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PerformanceInsertion {
|
|
||||||
pub person: Option<i64>,
|
|
||||||
pub ensemble: Option<i64>,
|
|
||||||
pub role: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bundle of everything needed for adding a new recording to the database.
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RecordingInsertion {
|
|
||||||
pub work: i64,
|
|
||||||
pub comment: String,
|
|
||||||
pub performances: Vec<PerformanceInsertion>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a new recording.
|
|
||||||
pub fn insert_recording(
|
|
||||||
conn: &DbConn,
|
|
||||||
id: u32,
|
|
||||||
data: &RecordingInsertion,
|
|
||||||
created_by: &str,
|
|
||||||
) -> Result<()> {
|
|
||||||
conn.transaction::<(), Error, _>(|| {
|
conn.transaction::<(), Error, _>(|| {
|
||||||
let id = id as i64;
|
let old_row = get_recording_row(conn, recording.id)?;
|
||||||
|
|
||||||
|
let allowed = match old_row {
|
||||||
|
Some(row) => user.may_edit(&row.created_by),
|
||||||
|
None => user.may_create(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
let id = recording.id as i64;
|
||||||
|
|
||||||
|
// This will also delete the old performances.
|
||||||
|
diesel::delete(recordings::table)
|
||||||
|
.filter(recordings::id.eq(id))
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
let row = RecordingRow {
|
||||||
|
id,
|
||||||
|
work: recording.work.id as i64,
|
||||||
|
comment: recording.comment.clone(),
|
||||||
|
created_by: user.username.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
diesel::insert_into(recordings::table)
|
diesel::insert_into(recordings::table)
|
||||||
.values(Recording {
|
.values(row)
|
||||||
id,
|
|
||||||
work: data.work,
|
|
||||||
comment: data.comment.clone(),
|
|
||||||
created_by: created_by.to_string(),
|
|
||||||
})
|
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
|
|
||||||
insert_recording_data(conn, id, data)?;
|
for performance in &recording.performances {
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update an existing recording.
|
|
||||||
pub fn update_recording(conn: &DbConn, id: u32, data: &RecordingInsertion) -> Result<()> {
|
|
||||||
conn.transaction::<(), Error, _>(|| {
|
|
||||||
let id = id as i64;
|
|
||||||
|
|
||||||
diesel::delete(performances::table)
|
|
||||||
.filter(performances::recording.eq(id))
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
diesel::update(recordings::table)
|
|
||||||
.filter(recordings::id.eq(id))
|
|
||||||
.set((
|
|
||||||
recordings::work.eq(data.work),
|
|
||||||
recordings::comment.eq(data.comment.clone()),
|
|
||||||
))
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
insert_recording_data(conn, id, data)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper method to populate other tables related to a recording.
|
|
||||||
fn insert_recording_data(conn: &DbConn, id: i64, data: &RecordingInsertion) -> Result<()> {
|
|
||||||
for performance in &data.performances {
|
|
||||||
diesel::insert_into(performances::table)
|
diesel::insert_into(performances::table)
|
||||||
.values(Performance {
|
.values(PerformanceRow {
|
||||||
id: rand::random(),
|
id: rand::random(),
|
||||||
recording: id,
|
recording: id,
|
||||||
person: performance.person,
|
person: performance.person.as_ref().map(|person| person.id as i64),
|
||||||
ensemble: performance.ensemble,
|
ensemble: performance
|
||||||
role: performance.role,
|
.ensemble
|
||||||
|
.as_ref()
|
||||||
|
.map(|ensemble| ensemble.id as i64),
|
||||||
|
role: performance.role.as_ref().map(|role| role.id as i64),
|
||||||
})
|
})
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an existing recording.
|
/// Get an existing recording and all available information from related tables.
|
||||||
pub fn get_recording(conn: &DbConn, id: u32) -> Result<Option<Recording>> {
|
pub fn get_recording(conn: &DbConn, id: u32) -> Result<Option<Recording>> {
|
||||||
|
let recording = match get_recording_row(conn, id)? {
|
||||||
|
Some(row) => Some(get_description_for_recording_row(conn, &row)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(recording)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all available information on all recordings where a person is performing.
|
||||||
|
pub fn get_recordings_for_person(conn: &DbConn, person_id: u32) -> Result<Vec<Recording>> {
|
||||||
|
let mut recordings: Vec<Recording> = Vec::new();
|
||||||
|
|
||||||
|
let rows = recordings::table
|
||||||
|
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
||||||
|
.inner_join(persons::table.on(persons::id.nullable().eq(performances::person)))
|
||||||
|
.filter(persons::id.eq(person_id as i64))
|
||||||
|
.select(recordings::table::all_columns())
|
||||||
|
.load::<RecordingRow>(conn)?;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
recordings.push(get_description_for_recording_row(conn, &row)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(recordings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all available information on all recordings where an ensemble is performing.
|
||||||
|
pub fn get_recordings_for_ensemble(conn: &DbConn, ensemble_id: u32) -> Result<Vec<Recording>> {
|
||||||
|
let mut recordings: Vec<Recording> = Vec::new();
|
||||||
|
|
||||||
|
let rows = recordings::table
|
||||||
|
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
||||||
|
.inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble)))
|
||||||
|
.filter(ensembles::id.eq(ensemble_id as i64))
|
||||||
|
.select(recordings::table::all_columns())
|
||||||
|
.load::<RecordingRow>(conn)?;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
recordings.push(get_description_for_recording_row(conn, &row)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(recordings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get allavailable information on all recordings of a work.
|
||||||
|
pub fn get_recordings_for_work(conn: &DbConn, work_id: u32) -> Result<Vec<Recording>> {
|
||||||
|
let mut recordings: Vec<Recording> = Vec::new();
|
||||||
|
|
||||||
|
let rows = recordings::table
|
||||||
|
.filter(recordings::work.eq(work_id as i64))
|
||||||
|
.load::<RecordingRow>(conn)?;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
recordings.push(get_description_for_recording_row(conn, &row)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(recordings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an existing recording. This will fail if there are still references to this
|
||||||
|
/// recording from other tables that are not directly part of the recording data. Also, the
|
||||||
|
/// provided user has to be allowed to delete the recording.
|
||||||
|
pub fn delete_recording(conn: &DbConn, id: u32, user: &User) -> Result<()> {
|
||||||
|
if user.may_delete() {
|
||||||
|
diesel::delete(recordings::table.filter(recordings::id.eq(id as i64))).execute(conn)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an existing recording row.
|
||||||
|
fn get_recording_row(conn: &DbConn, id: u32) -> Result<Option<RecordingRow>> {
|
||||||
Ok(recordings::table
|
Ok(recordings::table
|
||||||
.filter(recordings::id.eq(id as i64))
|
.filter(recordings::id.eq(id as i64))
|
||||||
.load::<Recording>(conn)?
|
.load::<RecordingRow>(conn)?
|
||||||
.first()
|
.into_iter()
|
||||||
.cloned())
|
.next())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve all available information on a recording from related tables.
|
/// Retrieve all available information on a recording from related tables.
|
||||||
pub fn get_description_for_recording(
|
fn get_description_for_recording_row(conn: &DbConn, row: &RecordingRow) -> Result<Recording> {
|
||||||
conn: &DbConn,
|
let mut performances: Vec<Performance> = Vec::new();
|
||||||
recording: &Recording,
|
|
||||||
) -> Result<RecordingDescription> {
|
|
||||||
let mut performance_descriptions: Vec<PerformanceDescription> = Vec::new();
|
|
||||||
|
|
||||||
let performances = performances::table
|
let performance_rows = performances::table
|
||||||
.filter(performances::recording.eq(recording.id))
|
.filter(performances::recording.eq(row.id))
|
||||||
.load::<Performance>(conn)?;
|
.load::<PerformanceRow>(conn)?;
|
||||||
|
|
||||||
for performance in performances {
|
for row in performance_rows {
|
||||||
performance_descriptions.push(PerformanceDescription {
|
performances.push(Performance {
|
||||||
person: match performance.person {
|
person: match row.person {
|
||||||
Some(id) => Some(
|
Some(id) => {
|
||||||
persons::table
|
let id = id as u32;
|
||||||
.filter(persons::id.eq(id as i64))
|
Some(get_person(conn, id)?.ok_or(anyhow!("No person with ID: {}", id))?)
|
||||||
.load::<Person>(conn)?
|
}
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(anyhow!("No person with ID: {}", id))?,
|
|
||||||
),
|
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
ensemble: match performance.ensemble {
|
ensemble: match row.ensemble {
|
||||||
Some(id) => Some(
|
Some(id) => {
|
||||||
ensembles::table
|
let id = id as u32;
|
||||||
.filter(ensembles::id.eq(id as i64))
|
Some(get_ensemble(conn, id)?.ok_or(anyhow!("No ensemble with ID: {}", id))?)
|
||||||
.load::<Ensemble>(conn)?
|
}
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(anyhow!("No ensemble with ID: {}", id))?,
|
|
||||||
),
|
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
role: match performance.role {
|
role: match row.role {
|
||||||
Some(id) => Some(
|
Some(id) => {
|
||||||
instruments::table
|
let id = id as u32;
|
||||||
.filter(instruments::id.eq(id as i64))
|
Some(
|
||||||
.load::<Instrument>(conn)?
|
get_instrument(conn, id)?
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(anyhow!("No instrument with ID: {}", id))?,
|
.ok_or(anyhow!("No instrument with ID: {}", id))?,
|
||||||
),
|
)
|
||||||
|
}
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let work_id = recording.work.try_into()?;
|
let id = row.work as u32;
|
||||||
let work =
|
let work = get_work(conn, id)?.ok_or(anyhow!("No work with ID: {}", id))?;
|
||||||
get_work_description(conn, work_id)?.ok_or(anyhow!("Work doesn't exist: {}", work_id))?;
|
|
||||||
|
|
||||||
let recording_description = RecordingDescription {
|
let recording = Recording {
|
||||||
id: recording.id,
|
id: row.id as u32,
|
||||||
work,
|
work,
|
||||||
comment: recording.comment.clone(),
|
comment: row.comment.clone(),
|
||||||
performances: performance_descriptions,
|
performances,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(recording_description)
|
Ok(recording)
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an existing recording and all available information from related tables.
|
|
||||||
pub fn get_recording_description(conn: &DbConn, id: u32) -> Result<Option<RecordingDescription>> {
|
|
||||||
let recording_description = match get_recording(conn, id)? {
|
|
||||||
Some(recording) => Some(get_description_for_recording(conn, &recording)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(recording_description)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all available information on all recordings where a person is performing.
|
|
||||||
pub fn get_recordings_for_person(
|
|
||||||
conn: &DbConn,
|
|
||||||
person_id: u32,
|
|
||||||
) -> Result<Vec<RecordingDescription>> {
|
|
||||||
let mut recording_descriptions: Vec<RecordingDescription> = Vec::new();
|
|
||||||
|
|
||||||
let recordings = recordings::table
|
|
||||||
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
|
||||||
.inner_join(persons::table.on(persons::id.nullable().eq(performances::person)))
|
|
||||||
.filter(persons::id.eq(person_id as i64))
|
|
||||||
.select(recordings::table::all_columns())
|
|
||||||
.load::<Recording>(conn)?;
|
|
||||||
|
|
||||||
for recording in recordings {
|
|
||||||
recording_descriptions.push(get_description_for_recording(conn, &recording)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(recording_descriptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all available information on all recordings where an ensemble is performing.
|
|
||||||
pub fn get_recordings_for_ensemble(
|
|
||||||
conn: &DbConn,
|
|
||||||
ensemble_id: u32,
|
|
||||||
) -> Result<Vec<RecordingDescription>> {
|
|
||||||
let mut recording_descriptions: Vec<RecordingDescription> = Vec::new();
|
|
||||||
|
|
||||||
let recordings = recordings::table
|
|
||||||
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
|
||||||
.inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble)))
|
|
||||||
.filter(ensembles::id.eq(ensemble_id as i64))
|
|
||||||
.select(recordings::table::all_columns())
|
|
||||||
.load::<Recording>(conn)?;
|
|
||||||
|
|
||||||
for recording in recordings {
|
|
||||||
recording_descriptions.push(get_description_for_recording(conn, &recording)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(recording_descriptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get allavailable information on all recordings of a work.
|
|
||||||
pub fn get_recordings_for_work(conn: &DbConn, work_id: u32) -> Result<Vec<RecordingDescription>> {
|
|
||||||
let mut recording_descriptions: Vec<RecordingDescription> = Vec::new();
|
|
||||||
|
|
||||||
let recordings = recordings::table
|
|
||||||
.filter(recordings::work.eq(work_id as i64))
|
|
||||||
.load::<Recording>(conn)?;
|
|
||||||
|
|
||||||
for recording in recordings {
|
|
||||||
recording_descriptions.push(get_description_for_recording(conn, &recording)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(recording_descriptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete an existing recording. This will fail if there are still references to this
|
|
||||||
/// recording from other tables that are not directly part of the recording data.
|
|
||||||
pub fn delete_recording(conn: &DbConn, id: u32) -> Result<()> {
|
|
||||||
diesel::delete(recordings::table.filter(recordings::id.eq(id as i64))).execute(conn)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,23 @@ pub struct User {
|
||||||
pub is_banned: bool,
|
pub is_banned: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
/// Check whether the user is allowed to create a new item.
|
||||||
|
pub fn may_create(&self) -> bool {
|
||||||
|
!self.is_banned
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the user is allowed to edit an item created by him or somebody else.
|
||||||
|
pub fn may_edit(&self, creator: &str) -> bool {
|
||||||
|
!self.is_banned && (self.username == creator || self.is_editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the user is allowed to delete an item.
|
||||||
|
pub fn may_delete(&self) -> bool {
|
||||||
|
!self.is_banned && self.is_editor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A structure representing data on a user.
|
/// A structure representing data on a user.
|
||||||
#[derive(AsChangeset, Deserialize, Debug, Clone)]
|
#[derive(AsChangeset, Deserialize, Debug, Clone)]
|
||||||
#[table_name = "users"]
|
#[table_name = "users"]
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,62 @@
|
||||||
use super::schema::{instrumentations, instruments, persons, work_parts, work_sections, works};
|
use super::schema::{instrumentations, work_parts, work_sections, works};
|
||||||
use super::{get_person, DbConn, Instrument, Person};
|
use super::{get_instrument, get_person, DbConn, Instrument, Person, User};
|
||||||
|
use crate::error::ServerError;
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::{Insertable, Queryable};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
/// A composition by a composer.
|
/// A specific work by a composer.
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Work {
|
pub struct Work {
|
||||||
|
pub id: u32,
|
||||||
|
pub title: String,
|
||||||
|
pub composer: Person,
|
||||||
|
pub instruments: Vec<Instrument>,
|
||||||
|
pub parts: Vec<WorkPart>,
|
||||||
|
pub sections: Vec<WorkSection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A playable part of a work.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct WorkPart {
|
||||||
|
pub title: String,
|
||||||
|
pub composer: Option<Person>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A heading within the work structure.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct WorkSection {
|
||||||
|
pub title: String,
|
||||||
|
pub before_index: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Table data for a work.
|
||||||
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
|
#[table_name = "works"]
|
||||||
|
struct WorkRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub composer: i64,
|
pub composer: i64,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub created_by: String,
|
pub created_by: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Definition that a work uses an instrument.
|
/// Table data for an instrumentation.
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
pub struct Instrumentation {
|
#[table_name = "instrumentations"]
|
||||||
|
struct InstrumentationRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub work: i64,
|
pub work: i64,
|
||||||
pub instrument: i64,
|
pub instrument: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A concrete work part that can be recorded.
|
/// Table data for a work part.
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
pub struct WorkPart {
|
#[table_name = "work_parts"]
|
||||||
|
struct WorkPartRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub work: i64,
|
pub work: i64,
|
||||||
pub part_index: i64,
|
pub part_index: i64,
|
||||||
|
|
@ -33,152 +64,73 @@ pub struct WorkPart {
|
||||||
pub composer: Option<i64>,
|
pub composer: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A heading between work parts.
|
/// Table data for a work section.
|
||||||
|
#[table_name = "work_sections"]
|
||||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||||
pub struct WorkSection {
|
struct WorkSectionRow {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub work: i64,
|
pub work: i64,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub before_index: i64,
|
pub before_index: i64,
|
||||||
}
|
}
|
||||||
/// A structure for collecting all available information on a work part.
|
|
||||||
#[derive(Serialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct WorkPartDescription {
|
|
||||||
pub title: String,
|
|
||||||
pub composer: Option<Person>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure for collecting all available information on a work section.
|
/// Update an existing work or insert a new one. This will only succeed, if the user is allowed to
|
||||||
#[derive(Serialize, Debug, Clone)]
|
/// do that.
|
||||||
#[serde(rename_all = "camelCase")]
|
// TODO: Also add newly created associated items.
|
||||||
pub struct WorkSectionDescription {
|
pub fn update_work(conn: &DbConn, work: &Work, user: &User) -> Result<()> {
|
||||||
pub title: String,
|
|
||||||
pub before_index: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure for collecting all available information on a work.
|
|
||||||
#[derive(Serialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct WorkDescription {
|
|
||||||
pub id: i64,
|
|
||||||
pub title: String,
|
|
||||||
pub composer: Person,
|
|
||||||
pub instruments: Vec<Instrument>,
|
|
||||||
pub parts: Vec<WorkPartDescription>,
|
|
||||||
pub sections: Vec<WorkSectionDescription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure representing data on a work part.
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct WorkPartInsertion {
|
|
||||||
pub title: String,
|
|
||||||
pub composer: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure representing data on a work section.
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct WorkSectionInsertion {
|
|
||||||
pub title: String,
|
|
||||||
pub before_index: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A structure representing data on a work.
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct WorkInsertion {
|
|
||||||
pub composer: i64,
|
|
||||||
pub title: String,
|
|
||||||
pub instruments: Vec<i64>,
|
|
||||||
pub parts: Vec<WorkPartInsertion>,
|
|
||||||
pub sections: Vec<WorkSectionInsertion>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a new work.
|
|
||||||
pub fn insert_work(conn: &DbConn, id: u32, data: &WorkInsertion, created_by: &str) -> Result<()> {
|
|
||||||
conn.transaction::<(), Error, _>(|| {
|
conn.transaction::<(), Error, _>(|| {
|
||||||
let id = id as i64;
|
let old_row = get_work_row(conn, work.id)?;
|
||||||
|
|
||||||
|
let allowed = match old_row {
|
||||||
|
Some(row) => user.may_edit(&row.created_by),
|
||||||
|
None => user.may_create(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
let id = work.id as i64;
|
||||||
|
|
||||||
|
// This will also delete rows from associated tables.
|
||||||
|
diesel::delete(works::table)
|
||||||
|
.filter(works::id.eq(id))
|
||||||
|
.execute(conn)?;
|
||||||
|
|
||||||
|
let row = WorkRow {
|
||||||
|
id,
|
||||||
|
composer: work.composer.id as i64,
|
||||||
|
title: work.title.clone(),
|
||||||
|
created_by: user.username.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
diesel::insert_into(works::table)
|
diesel::insert_into(works::table)
|
||||||
.values(Work {
|
.values(row)
|
||||||
id,
|
|
||||||
composer: data.composer.clone(),
|
|
||||||
title: data.title.clone(),
|
|
||||||
created_by: created_by.to_string(),
|
|
||||||
})
|
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
|
|
||||||
insert_work_data(conn, id, data)?;
|
for instrument in &work.instruments {
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update an existing work.
|
|
||||||
pub fn update_work(conn: &DbConn, id: u32, data: &WorkInsertion) -> Result<()> {
|
|
||||||
conn.transaction::<(), Error, _>(|| {
|
|
||||||
let id = id as i64;
|
|
||||||
|
|
||||||
diesel::delete(instrumentations::table)
|
|
||||||
.filter(instrumentations::work.eq(id))
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
diesel::delete(work_parts::table)
|
|
||||||
.filter(work_parts::work.eq(id))
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
diesel::delete(work_sections::table)
|
|
||||||
.filter(work_sections::work.eq(id))
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
diesel::update(works::table)
|
|
||||||
.filter(works::id.eq(id))
|
|
||||||
.set((
|
|
||||||
works::composer.eq(data.composer),
|
|
||||||
works::title.eq(data.title.clone()),
|
|
||||||
))
|
|
||||||
.execute(conn)?;
|
|
||||||
|
|
||||||
insert_work_data(conn, id, data)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper method to populate tables related to a work.
|
|
||||||
fn insert_work_data(conn: &DbConn, id: i64, data: &WorkInsertion) -> Result<()> {
|
|
||||||
for instrument in &data.instruments {
|
|
||||||
diesel::insert_into(instrumentations::table)
|
diesel::insert_into(instrumentations::table)
|
||||||
.values(Instrumentation {
|
.values(InstrumentationRow {
|
||||||
id: rand::random(),
|
id: rand::random(),
|
||||||
work: id,
|
work: id,
|
||||||
instrument: *instrument,
|
instrument: instrument.id as i64,
|
||||||
})
|
})
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index, part) in data.parts.iter().enumerate() {
|
for (index, part) in work.parts.iter().enumerate() {
|
||||||
let part = WorkPart {
|
let row = WorkPartRow {
|
||||||
id: rand::random(),
|
id: rand::random(),
|
||||||
work: id,
|
work: id,
|
||||||
part_index: index.try_into()?,
|
part_index: index.try_into()?,
|
||||||
title: part.title.clone(),
|
title: part.title.clone(),
|
||||||
composer: part.composer,
|
composer: part.composer.as_ref().map(|person| person.id as i64),
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(work_parts::table)
|
diesel::insert_into(work_parts::table)
|
||||||
.values(part)
|
.values(row)
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for section in &data.sections {
|
for section in &work.sections {
|
||||||
let section = WorkSection {
|
let row = WorkSectionRow {
|
||||||
id: rand::random(),
|
id: rand::random(),
|
||||||
work: id,
|
work: id,
|
||||||
title: section.title.clone(),
|
title: section.title.clone(),
|
||||||
|
|
@ -186,122 +138,120 @@ fn insert_work_data(conn: &DbConn, id: i64, data: &WorkInsertion) -> Result<()>
|
||||||
};
|
};
|
||||||
|
|
||||||
diesel::insert_into(work_sections::table)
|
diesel::insert_into(work_sections::table)
|
||||||
.values(section)
|
.values(row)
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an existing work and all available information from related tables.
|
||||||
|
pub fn get_work(conn: &DbConn, id: u32) -> Result<Option<Work>> {
|
||||||
|
let work = match get_work_row(conn, id)? {
|
||||||
|
Some(row) => Some(get_description_for_work_row(conn, &row)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(work)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete an existing work. This will fail if there are still other tables that relate to
|
||||||
|
/// this work except for the things that are part of the information on the work itself. Also,
|
||||||
|
/// this will only succeed, if the provided user is allowed to delete the work.
|
||||||
|
pub fn delete_work(conn: &DbConn, id: u32, user: &User) -> Result<()> {
|
||||||
|
if user.may_delete() {
|
||||||
|
diesel::delete(works::table.filter(works::id.eq(id as i64))).execute(conn)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(ServerError::Forbidden))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all existing works by a composer and related information from other tables.
|
||||||
|
pub fn get_works(conn: &DbConn, composer_id: u32) -> Result<Vec<Work>> {
|
||||||
|
let mut works: Vec<Work> = Vec::new();
|
||||||
|
|
||||||
|
let rows = works::table
|
||||||
|
.filter(works::composer.eq(composer_id as i64))
|
||||||
|
.load::<WorkRow>(conn)?;
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
works.push(get_description_for_work_row(conn, &row)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(works)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an already existing work without related rows from other tables.
|
/// Get an already existing work without related rows from other tables.
|
||||||
fn get_work(conn: &DbConn, id: u32) -> Result<Option<Work>> {
|
fn get_work_row(conn: &DbConn, id: u32) -> Result<Option<WorkRow>> {
|
||||||
Ok(works::table
|
Ok(works::table
|
||||||
.filter(works::id.eq(id as i64))
|
.filter(works::id.eq(id as i64))
|
||||||
.load::<Work>(conn)?
|
.load::<WorkRow>(conn)?
|
||||||
.first()
|
.into_iter()
|
||||||
.cloned())
|
.next())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve all available information on a work from related tables.
|
/// Retrieve all available information on a work from related tables.
|
||||||
fn get_description_for_work(conn: &DbConn, work: &Work) -> Result<WorkDescription> {
|
fn get_description_for_work_row(conn: &DbConn, row: &WorkRow) -> Result<Work> {
|
||||||
let mut instruments: Vec<Instrument> = Vec::new();
|
let mut instruments: Vec<Instrument> = Vec::new();
|
||||||
|
|
||||||
let instrumentations = instrumentations::table
|
let instrumentations = instrumentations::table
|
||||||
.filter(instrumentations::work.eq(work.id))
|
.filter(instrumentations::work.eq(row.id))
|
||||||
.load::<Instrumentation>(conn)?;
|
.load::<InstrumentationRow>(conn)?;
|
||||||
|
|
||||||
for instrumentation in instrumentations {
|
for instrumentation in instrumentations {
|
||||||
instruments.push(
|
let id = instrumentation.instrument as u32;
|
||||||
instruments::table
|
instruments
|
||||||
.filter(instruments::id.eq(instrumentation.instrument))
|
.push(get_instrument(conn, id)?.ok_or(anyhow!("No instrument with ID: {}", id))?);
|
||||||
.load::<Instrument>(conn)?
|
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(anyhow!(
|
|
||||||
"No instrument with ID: {}",
|
|
||||||
instrumentation.instrument
|
|
||||||
))?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut part_descriptions: Vec<WorkPartDescription> = Vec::new();
|
let mut parts: Vec<WorkPart> = Vec::new();
|
||||||
|
|
||||||
let work_parts = work_parts::table
|
let part_rows = work_parts::table
|
||||||
.filter(work_parts::work.eq(work.id))
|
.filter(work_parts::work.eq(row.id))
|
||||||
.load::<WorkPart>(conn)?;
|
.load::<WorkPartRow>(conn)?;
|
||||||
|
|
||||||
for work_part in work_parts {
|
for part_row in part_rows {
|
||||||
part_descriptions.push(WorkPartDescription {
|
parts.push(WorkPart {
|
||||||
title: work_part.title,
|
title: part_row.title,
|
||||||
composer: match work_part.composer {
|
composer: match part_row.composer {
|
||||||
Some(composer) => Some(
|
Some(id) => {
|
||||||
persons::table
|
let id = id as u32;
|
||||||
.filter(persons::id.eq(composer))
|
Some(get_person(conn, id)?.ok_or(anyhow!("No person with ID: {}", id))?)
|
||||||
.load::<Person>(conn)?
|
}
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(anyhow!("No person with ID: {}", composer))?,
|
|
||||||
),
|
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut section_descriptions: Vec<WorkSectionDescription> = Vec::new();
|
let mut sections: Vec<WorkSection> = Vec::new();
|
||||||
|
|
||||||
let sections = work_sections::table
|
let section_rows = work_sections::table
|
||||||
.filter(work_sections::work.eq(work.id))
|
.filter(work_sections::work.eq(row.id))
|
||||||
.load::<WorkSection>(conn)?;
|
.load::<WorkSectionRow>(conn)?;
|
||||||
|
|
||||||
for section in sections {
|
for section in section_rows {
|
||||||
section_descriptions.push(WorkSectionDescription {
|
sections.push(WorkSection {
|
||||||
title: section.title,
|
title: section.title,
|
||||||
before_index: section.before_index,
|
before_index: section.before_index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let person_id = work.composer.try_into()?;
|
let id = row.composer as u32;
|
||||||
let person =
|
let composer = get_person(conn, id)?.ok_or(anyhow!("No person with ID: {}", id))?;
|
||||||
get_person(conn, person_id)?.ok_or(anyhow!("Person doesn't exist: {}", person_id))?;
|
|
||||||
|
|
||||||
Ok(WorkDescription {
|
Ok(Work {
|
||||||
id: work.id,
|
id: row.id as u32,
|
||||||
composer: person,
|
composer,
|
||||||
title: work.title.clone(),
|
title: row.title.clone(),
|
||||||
instruments,
|
instruments,
|
||||||
parts: part_descriptions,
|
parts,
|
||||||
sections: section_descriptions,
|
sections,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an existing work and all available information from related tables.
|
|
||||||
pub fn get_work_description(conn: &DbConn, id: u32) -> Result<Option<WorkDescription>> {
|
|
||||||
let work_description = match get_work(conn, id)? {
|
|
||||||
Some(work) => Some(get_description_for_work(conn, &work)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(work_description)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete an existing work. This will fail if there are still other tables that relate to
|
|
||||||
/// this work except for the things that are part of the information on the work it
|
|
||||||
pub fn delete_work(conn: &DbConn, id: u32) -> Result<()> {
|
|
||||||
diesel::delete(works::table.filter(works::id.eq(id as i64))).execute(conn)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all existing works by a composer and related information from other tables.
|
|
||||||
pub fn get_work_descriptions(conn: &DbConn, composer_id: u32) -> Result<Vec<WorkDescription>> {
|
|
||||||
let mut work_descriptions: Vec<WorkDescription> = Vec::new();
|
|
||||||
|
|
||||||
let works = works::table
|
|
||||||
.filter(works::composer.eq(composer_id as i64))
|
|
||||||
.load::<Work>(conn)?;
|
|
||||||
|
|
||||||
for work in works {
|
|
||||||
work_descriptions.push(get_description_for_work(conn, &work)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(work_descriptions)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,17 @@ impl error::ResponseError for ServerError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<r2d2::Error> for ServerError {
|
impl From<r2d2::Error> for ServerError {
|
||||||
fn from(error: r2d2::Error) -> Self {
|
fn from(_: r2d2::Error) -> Self {
|
||||||
ServerError::Internal
|
ServerError::Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<anyhow::Error> for ServerError {
|
impl From<anyhow::Error> for ServerError {
|
||||||
fn from(error: anyhow::Error) -> Self {
|
fn from(error: anyhow::Error) -> Self {
|
||||||
ServerError::Internal
|
match error.downcast() {
|
||||||
|
Ok(error) => error,
|
||||||
|
Err(_) => ServerError::Internal,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5,6 +5,7 @@ extern crate diesel;
|
||||||
use actix_web::{App, HttpServer};
|
use actix_web::{App, HttpServer};
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
|
mod error;
|
||||||
|
|
||||||
mod routes;
|
mod routes;
|
||||||
use routes::*;
|
use routes::*;
|
||||||
|
|
@ -27,9 +28,9 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(put_user)
|
.service(put_user)
|
||||||
.service(get_user)
|
.service(get_user)
|
||||||
.service(get_person)
|
.service(get_person)
|
||||||
.service(post_person)
|
.service(update_person)
|
||||||
.service(put_person)
|
|
||||||
.service(get_persons)
|
.service(get_persons)
|
||||||
|
.service(delete_person)
|
||||||
});
|
});
|
||||||
|
|
||||||
server.bind("127.0.0.1:8087")?.run().await
|
server.bind("127.0.0.1:8087")?.run().await
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use super::ServerError;
|
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::database::{DbConn, DbPool, User, UserInsertion};
|
use crate::database::{DbConn, DbPool, User, UserInsertion};
|
||||||
|
use crate::error::ServerError;
|
||||||
use actix_web::{get, post, put, web, HttpResponse};
|
use actix_web::{get, post, put, web, HttpResponse};
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
@ -167,21 +167,6 @@ pub fn authenticate(conn: &DbConn, token: &str) -> Result<User> {
|
||||||
database::get_user(conn, &username)?.ok_or(anyhow!("User doesn't exist: {}", &username))
|
database::get_user(conn, &username)?.ok_or(anyhow!("User doesn't exist: {}", &username))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the user is allowed to create a new item.
|
|
||||||
pub fn may_create(user: &User) -> bool {
|
|
||||||
!user.is_banned
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether the user is allowed to edit an item created by him or somebody else.
|
|
||||||
pub fn may_edit(user: &User, created_by: &str) -> bool {
|
|
||||||
!user.is_banned && (user.username == created_by || user.is_editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether the user is allowed to delete an item.
|
|
||||||
pub fn may_delete(user: &User) -> bool {
|
|
||||||
!user.is_banned && user.is_editor
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a hash for a password that can be stored in the database.
|
/// Return a hash for a password that can be stored in the database.
|
||||||
fn hash_password(password: &str) -> Result<String> {
|
fn hash_password(password: &str) -> Result<String> {
|
||||||
let hash = argon2id13::pwhash(
|
let hash = argon2id13::pwhash(
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@ pub use auth::*;
|
||||||
pub mod ensembles;
|
pub mod ensembles;
|
||||||
pub use ensembles::*;
|
pub use ensembles::*;
|
||||||
|
|
||||||
pub mod error;
|
|
||||||
pub use error::*;
|
|
||||||
|
|
||||||
pub mod instruments;
|
pub mod instruments;
|
||||||
pub use instruments::*;
|
pub use instruments::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use super::{authenticate, may_create, may_delete, may_edit, ServerError};
|
use super::authenticate;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::database::{DbPool, PersonInsertion};
|
use crate::database::{DbPool, Person};
|
||||||
use actix_web::{delete, get, post, put, web, HttpResponse};
|
use crate::error::ServerError;
|
||||||
|
use actix_web::{delete, get, post, web, HttpResponse};
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
|
|
||||||
/// Get an existing person.
|
/// Get an existing person.
|
||||||
|
|
@ -10,60 +11,29 @@ pub async fn get_person(
|
||||||
db: web::Data<DbPool>,
|
db: web::Data<DbPool>,
|
||||||
id: web::Path<u32>,
|
id: web::Path<u32>,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
let person = web::block(move || {
|
let data = web::block(move || {
|
||||||
let conn = db.into_inner().get()?;
|
let conn = db.into_inner().get()?;
|
||||||
database::get_person(&conn, id.into_inner())?.ok_or(ServerError::NotFound)
|
database::get_person(&conn, id.into_inner())?.ok_or(ServerError::NotFound)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(person))
|
Ok(HttpResponse::Ok().json(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new person. The user must be authorized to do that.
|
/// Add a new person or update an existin one. The user must be authorized to do that.
|
||||||
#[post("/persons")]
|
#[post("/persons")]
|
||||||
pub async fn post_person(
|
pub async fn update_person(
|
||||||
auth: BearerAuth,
|
auth: BearerAuth,
|
||||||
db: web::Data<DbPool>,
|
db: web::Data<DbPool>,
|
||||||
data: web::Json<PersonInsertion>,
|
data: web::Json<Person>,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
|
||||||
let id = rand::random();
|
|
||||||
|
|
||||||
web::block(move || {
|
|
||||||
let conn = db.into_inner().get()?;
|
|
||||||
let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
|
|
||||||
if may_create(&user) {
|
|
||||||
database::insert_person(&conn, id, &data.into_inner(), &user.username)?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ServerError::Forbidden)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().body(id.to_string()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("/persons/{id}")]
|
|
||||||
pub async fn put_person(
|
|
||||||
auth: BearerAuth,
|
|
||||||
db: web::Data<DbPool>,
|
|
||||||
id: web::Path<u32>,
|
|
||||||
data: web::Json<PersonInsertion>,
|
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
web::block(move || {
|
web::block(move || {
|
||||||
let conn = db.into_inner().get()?;
|
let conn = db.into_inner().get()?;
|
||||||
|
|
||||||
let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
|
let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
|
||||||
|
|
||||||
let id = id.into_inner();
|
database::update_person(&conn, &data.into_inner(), &user)?;
|
||||||
let old_person = database::get_person(&conn, id)?.ok_or(ServerError::NotFound)?;
|
|
||||||
|
|
||||||
if may_edit(&user, &old_person.created_by) {
|
|
||||||
database::update_person(&conn, id, &data.into_inner())?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
Err(ServerError::Forbidden)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -72,13 +42,13 @@ pub async fn put_person(
|
||||||
|
|
||||||
#[get("/persons")]
|
#[get("/persons")]
|
||||||
pub async fn get_persons(db: web::Data<DbPool>) -> Result<HttpResponse, ServerError> {
|
pub async fn get_persons(db: web::Data<DbPool>) -> Result<HttpResponse, ServerError> {
|
||||||
let persons = web::block(move || {
|
let data = web::block(move || {
|
||||||
let conn = db.into_inner().get()?;
|
let conn = db.into_inner().get()?;
|
||||||
Ok(database::get_persons(&conn)?)
|
Ok(database::get_persons(&conn)?)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(persons))
|
Ok(HttpResponse::Ok().json(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/persons/{id}")]
|
#[delete("/persons/{id}")]
|
||||||
|
|
@ -91,12 +61,9 @@ pub async fn delete_person(
|
||||||
let conn = db.into_inner().get()?;
|
let conn = db.into_inner().get()?;
|
||||||
let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
|
let user = authenticate(&conn, auth.token()).or(Err(ServerError::Unauthorized))?;
|
||||||
|
|
||||||
if may_delete(&user) {
|
database::delete_person(&conn, id.into_inner(), &user)?;
|
||||||
database::delete_person(&conn, id.into_inner())?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
|
||||||
Err(ServerError::Forbidden)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue