create & getall for tags

This commit is contained in:
2026-03-06 15:33:39 +01:00
parent 2ebbc88c0a
commit 9931bbe306
4 changed files with 49 additions and 0 deletions

View File

@@ -33,6 +33,8 @@ pub fn api_router() -> Router {
.route("/api/sessions/{id}", get(sessions::get_by_id)) .route("/api/sessions/{id}", get(sessions::get_by_id))
.route("/api/sessions/{id}/revoke", post(sessions::revoke_by_id)) .route("/api/sessions/{id}/revoke", post(sessions::revoke_by_id))
// //
.route("/api/tags", get(tags::get_all))
.route("/api/tags", post(tags::create))
.route("/api/tags/{id}", get(tags::get_by_id)) .route("/api/tags/{id}", get(tags::get_by_id))
.route("/api/tags/#{name}", get(tags::get_by_name)) .route("/api/tags/#{name}", get(tags::get_by_name))
} }

View File

@@ -4,6 +4,7 @@ use axum::{
http::HeaderMap, http::HeaderMap,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use serde::Deserialize;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
@@ -15,6 +16,11 @@ use crate::{
}, },
}; };
pub async fn get_all(headers: HeaderMap) -> Result<Response, CompositeError> {
User::authenticate(&headers)?.required()?;
Ok(Json(Tag::get_all()?).into_response())
}
pub async fn get_by_id( pub async fn get_by_id(
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
headers: HeaderMap, headers: HeaderMap,
@@ -30,3 +36,15 @@ pub async fn get_by_name(
User::authenticate(&headers)?.required()?; User::authenticate(&headers)?.required()?;
Ok(Json(Tag::get_by_name(name)?).into_response()) Ok(Json(Tag::get_by_name(name)?).into_response())
} }
#[derive(Deserialize)]
pub struct NewTag {
name: TagName,
}
pub async fn create(
headers: HeaderMap,
Json(form): Json<NewTag>,
) -> Result<Response, CompositeError> {
User::authenticate(&headers)?.required()?;
Ok(Json(Tag::create(form.name)?).into_response())
}

View File

@@ -6,6 +6,7 @@ use axum::{
}; };
use rusqlite::{ use rusqlite::{
OptionalExtension, Result as RusqliteResult, ToSql, OptionalExtension, Result as RusqliteResult, ToSql,
ffi::SQLITE_CONSTRAINT_UNIQUE,
types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}, types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -20,6 +21,17 @@ pub struct Tag {
} }
impl Tag { impl Tag {
pub fn get_all() -> Result<Vec<Tag>, TagError> {
Ok(database::conn()?
.prepare("SELECT id, tagname FROM tags")?
.query_map((), |r| {
Ok(Tag {
id: r.get(0)?,
name: r.get(1)?,
})
})?
.collect::<Result<Vec<Tag>, _>>()?)
}
pub fn get_by_id(id: Uuid) -> Result<Tag, TagError> { pub fn get_by_id(id: Uuid) -> Result<Tag, TagError> {
let res = database::conn()? let res = database::conn()?
.prepare("SELECT tagname FROM tags WHERE id = ?1")? .prepare("SELECT tagname FROM tags WHERE id = ?1")?
@@ -50,6 +62,13 @@ impl Tag {
None => Err(TagError::NoTagWithName(name)), None => Err(TagError::NoTagWithName(name)),
} }
} }
pub fn create(name: TagName) -> Result<Tag, TagError> {
let id = Uuid::now_v7();
database::conn()?
.prepare("INSERT INTO tags(id, tagname) VALUES (?1, ?2)")?
.execute((id, &name))?;
Ok(Tag { id, name })
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@@ -60,11 +79,19 @@ pub enum TagError {
NoTagWithId(Uuid), NoTagWithId(Uuid),
#[error("No tag found with name {0}")] #[error("No tag found with name {0}")]
NoTagWithName(TagName), NoTagWithName(TagName),
#[error("A tag with this name already exists")]
TagAlreadyExists,
#[error("Database error: {0}")] #[error("Database error: {0}")]
DatabaseError(#[from] DatabaseError), DatabaseError(#[from] DatabaseError),
} }
impl From<rusqlite::Error> for TagError { impl From<rusqlite::Error> for TagError {
fn from(error: rusqlite::Error) -> Self { fn from(error: rusqlite::Error) -> Self {
if let rusqlite::Error::SqliteFailure(e, Some(msg)) = &error
&& e.extended_code == SQLITE_CONSTRAINT_UNIQUE
&& msg.contains("tagname")
{
return TagError::TagAlreadyExists;
}
TagError::DatabaseError(DatabaseError::from(error)) TagError::DatabaseError(DatabaseError::from(error))
} }
} }
@@ -72,6 +99,7 @@ impl IntoResponse for TagError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
match self { match self {
Self::DatabaseError(e) => e.into_response(), Self::DatabaseError(e) => e.into_response(),
Self::TagAlreadyExists => (StatusCode::CONFLICT, self.to_string()).into_response(),
Self::TagNameError(_) => (StatusCode::BAD_REQUEST, self.to_string()).into_response(), Self::TagNameError(_) => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),
Self::NoTagWithId(_) => (StatusCode::BAD_REQUEST, self.to_string()).into_response(), Self::NoTagWithId(_) => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),
Self::NoTagWithName(_) => (StatusCode::BAD_REQUEST, self.to_string()).into_response(), Self::NoTagWithName(_) => (StatusCode::BAD_REQUEST, self.to_string()).into_response(),

View File

@@ -11,6 +11,7 @@ pub enum Permission {
// All Users have the right to change their own handle // All Users have the right to change their own handle
ChangeOthersHandles, ChangeOthersHandles,
ManuallyCreateUsers, ManuallyCreateUsers,
CreateTags,
} }
impl User { impl User {