database work
This commit is contained in:
parent
b7a2265e9b
commit
469a3ad339
|
@ -306,10 +306,13 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
|||
name = "critch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"poem",
|
||||
"ructe",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"time",
|
||||
"time-humanize",
|
||||
"tokio",
|
||||
"toml",
|
||||
"uuid",
|
||||
|
@ -1798,6 +1801,7 @@ dependencies = [
|
|||
"smallvec",
|
||||
"sqlformat",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
|
@ -1882,6 +1886,7 @@ dependencies = [
|
|||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
|
@ -1921,6 +1926,7 @@ dependencies = [
|
|||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
|
@ -1945,6 +1951,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
|
@ -2058,6 +2065,12 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-humanize"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e32d019b4f7c100bcd5494e40a27119d45b71fba2b07a4684153129279a4647"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.18"
|
||||
|
@ -2298,6 +2311,9 @@ name = "uuid"
|
|||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
|
|
|
@ -10,9 +10,12 @@ build = "src/build.rs"
|
|||
ructe = { version = "0.17.2", features = ["sass", "mime03"] }
|
||||
|
||||
[dependencies]
|
||||
mime = "0.3.17"
|
||||
poem = { version = "3.1.3", features = ["session"] }
|
||||
serde = "1.0.215"
|
||||
sqlx = { version = "0.8.2", features = ["uuid", "postgres", "runtime-tokio"] }
|
||||
sqlx = { version = "0.8.2", features = ["uuid", "postgres", "runtime-tokio", "time"] }
|
||||
time = "0.3.36"
|
||||
time-humanize = "0.1.3"
|
||||
tokio = { version = "1.41.1", features = ["full"] }
|
||||
toml = { version = "0.8.19", features = ["parse"] }
|
||||
uuid = "1.11.0"
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
|
|
@ -2,7 +2,8 @@ create extension if not exists "uuid-ossp";
|
|||
|
||||
create table artists (
|
||||
id integer primary key generated always as identity,
|
||||
artist_name varchar(128) not null unique,
|
||||
handle varchar(128) not null unique,
|
||||
name varchar(128),
|
||||
bio text,
|
||||
site varchar(256)
|
||||
);
|
||||
|
@ -12,6 +13,7 @@ create table artworks (
|
|||
title varchar(256),
|
||||
description text,
|
||||
url_source varchar(256),
|
||||
created_at timestamp not null default current_timestamp,
|
||||
artist_id integer not null,
|
||||
comment_number integer not null default 0,
|
||||
foreign key (artist_id) references artists(id)
|
||||
|
@ -20,24 +22,26 @@ create table artworks (
|
|||
create table comments (
|
||||
id integer unique not null,
|
||||
text text not null,
|
||||
thread_id integer not null,
|
||||
primary key (id, thread_id),
|
||||
foreign key (thread_id) references artworks(id)
|
||||
artwork_id integer not null,
|
||||
created_at timestamp not null default current_timestamp,
|
||||
primary key (id, artwork_id),
|
||||
foreign key (artwork_id) references artworks(id)
|
||||
);
|
||||
|
||||
create table comment_relations (
|
||||
thread_id integer,
|
||||
foreign key (thread_id) references artworks(id),
|
||||
artwork_id integer,
|
||||
foreign key (artwork_id) references artworks(id),
|
||||
in_reply_to_id integer,
|
||||
foreign key (in_reply_to_id) references comments(id),
|
||||
comment_id integer,
|
||||
foreign key (comment_id) references comments(id),
|
||||
primary key (thread_id, in_reply_to_id, comment_id)
|
||||
primary key (artwork_id, in_reply_to_id, comment_id)
|
||||
);
|
||||
|
||||
create table files (
|
||||
create table artwork_files (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
alt_text text,
|
||||
extension varchar(16),
|
||||
artwork_id integer,
|
||||
foreign key (artwork_id) references artworks(id)
|
||||
);
|
||||
|
|
|
@ -1,6 +1,17 @@
|
|||
use crate::error::Error;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
pub struct Artist {
|
||||
id: Option<usize>,
|
||||
name: String,
|
||||
bio: Option<String>,
|
||||
site: Option<String>,
|
||||
id: Option<i32>,
|
||||
pub handle: String,
|
||||
pub name: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub site: Option<String>,
|
||||
}
|
||||
|
||||
impl Artist {
|
||||
pub fn id(&self) -> Option<i32> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,27 @@
|
|||
use time::{OffsetDateTime, PrimitiveDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{artist::Artist, comment::Comment, file::File};
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
pub struct Artwork {
|
||||
/// artwork id
|
||||
id: Option<usize>,
|
||||
id: Option<i32>,
|
||||
/// name of the artwork
|
||||
title: Option<String>,
|
||||
pub title: Option<String>,
|
||||
/// description of the artwork
|
||||
description: Option<String>,
|
||||
pub description: Option<String>,
|
||||
/// source url of the artwork
|
||||
url_source: Option<String>,
|
||||
pub url_source: Option<String>,
|
||||
/// artwork creation time
|
||||
created_at: Option<PrimitiveDateTime>,
|
||||
/// id of the artist
|
||||
artist_id: usize,
|
||||
#[sqlx(Flatten)]
|
||||
pub artist: Artist,
|
||||
/// ids of files
|
||||
files: Vec<usize>,
|
||||
#[sqlx(Flatten)]
|
||||
pub files: Vec<File>,
|
||||
// /// TODO: comments in thread,
|
||||
// #[sqlx(Flatten)]
|
||||
// comments: Vec<Comment>,
|
||||
}
|
||||
|
|
|
@ -1,12 +1,34 @@
|
|||
use time::OffsetDateTime;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
pub struct Comment {
|
||||
/// id of the comment in the thread
|
||||
id: Option<usize>,
|
||||
id: Option<i32>,
|
||||
/// text of the comment
|
||||
text: String,
|
||||
/// thread comment is in
|
||||
thread: usize,
|
||||
pub text: String,
|
||||
/// id of artwork thread comment is in
|
||||
pub artwork_id: i32,
|
||||
/// comment creation time
|
||||
created_at: Option<OffsetDateTime>,
|
||||
/// comments that are mentioned by the comment
|
||||
in_reply_to: Vec<usize>,
|
||||
pub in_reply_to_ids: Vec<i32>,
|
||||
/// comments that mention the comment
|
||||
mentioned_by: Vec<usize>,
|
||||
mentioned_by_ids: Vec<i32>,
|
||||
}
|
||||
|
||||
impl Comment {
|
||||
pub fn id(&self) -> Option<i32> {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn created_at(&self) -> Option<OffsetDateTime> {
|
||||
self.created_at
|
||||
}
|
||||
|
||||
pub fn mentioned_by_ids(&self) -> &Vec<i32> {
|
||||
&self.mentioned_by_ids
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::artist::Artist;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Artists(Pool<Postgres>);
|
||||
|
||||
impl Artists {
|
||||
pub fn new(pool: Pool<Postgres>) -> Self {
|
||||
Self(pool)
|
||||
}
|
||||
|
||||
pub async fn create(&self, artist: Artist) -> Result<i32> {
|
||||
let artist_id = sqlx::query!(
|
||||
"insert into artists (handle, name, bio, site) values ($1, $2, $3, $4) returning id",
|
||||
artist.handle,
|
||||
artist.name,
|
||||
artist.bio,
|
||||
artist.site
|
||||
)
|
||||
.fetch_one(&self.0)
|
||||
.await?
|
||||
.id;
|
||||
Ok(artist_id)
|
||||
}
|
||||
|
||||
pub async fn read(&self, id: i32) -> Result<Artist> {
|
||||
Ok(sqlx::query_as("select * from artists where id = $1")
|
||||
.bind(id)
|
||||
.fetch_one(&self.0)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn read_handle(&self, handle: &str) -> Result<Artist> {
|
||||
Ok(sqlx::query_as("select * from artists where handle = $1")
|
||||
.bind(handle)
|
||||
.fetch_one(&self.0)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn read_all(&self) -> Result<Vec<Artist>> {
|
||||
Ok(sqlx::query_as("select * from artists")
|
||||
.fetch_all(&self.0)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn search(&self, query: &str) -> Result<Vec<Artist>> {
|
||||
Ok(
|
||||
sqlx::query_as("select * from artists where handle + name like '%$1%'")
|
||||
.bind(query)
|
||||
.fetch_all(&self.0)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1 +1,52 @@
|
|||
use sqlx::{Pool, Postgres};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::artist::Artist;
|
||||
use crate::artwork::Artwork;
|
||||
use crate::file::File;
|
||||
use crate::Result;
|
||||
|
||||
use super::Database;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Artworks(Pool<Postgres>);
|
||||
|
||||
impl Artworks {
|
||||
pub fn new(pool: Pool<Postgres>) -> Self {
|
||||
Self(pool)
|
||||
}
|
||||
|
||||
pub fn downcast(&self) -> Database {
|
||||
Database(self.0.clone())
|
||||
}
|
||||
|
||||
pub async fn create(&self, artwork: Artwork) -> Result<i32> {
|
||||
let artist_id = if let Some(artist_id) = artwork.artist.id() {
|
||||
artist_id
|
||||
} else {
|
||||
self.downcast().artists().create(artwork.artist).await?
|
||||
};
|
||||
let artwork_id = sqlx::query!("insert into artworks (title, description, url_source, artist_id) values ($1, $2, $3, $4) returning id", artwork.title, artwork.description, artwork.url_source, artist_id).fetch_one(&self.0).await?.id;
|
||||
for file in artwork.files {
|
||||
sqlx::query!(
|
||||
"insert into artwork_files (id, alt_text, extension, artwork_id) values ($1, $2, $3, $4)",
|
||||
file.id(),
|
||||
file.alt_text,
|
||||
file.extension(),
|
||||
artwork_id
|
||||
)
|
||||
.execute(&self.0)
|
||||
.await?;
|
||||
}
|
||||
Ok(artwork_id)
|
||||
}
|
||||
|
||||
pub async fn read_all(&self) -> Result<Vec<Artwork>> {
|
||||
// TODO: join comments and files
|
||||
Ok(sqlx::query_as(
|
||||
"select * from artworks left join artists on artworks.artist_id = artists.id left join artwork_files on artworks.id = artwork_files.artwork_id group by artworks.id, artists.id, artwork_files.id",
|
||||
)
|
||||
.fetch_all(&self.0)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::comment::Comment;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Comments(Pool<Postgres>);
|
||||
|
||||
impl Comments {
|
||||
pub fn new(pool: Pool<Postgres>) -> Self {
|
||||
Self(pool)
|
||||
}
|
||||
|
||||
pub async fn create(&self, comment: Comment) -> Result<i32> {
|
||||
let comment_id = sqlx::query!(
|
||||
r#"insert into comments (text, artwork_id) values ($1, $2) returning id"#,
|
||||
comment.text,
|
||||
comment.artwork_id
|
||||
)
|
||||
.fetch_one(&self.0)
|
||||
.await?
|
||||
.id;
|
||||
for in_reply_to_id in comment.in_reply_to_ids {
|
||||
sqlx::query!("insert into comment_relations (artwork_id, in_reply_to_id, comment_id) values ($1, $2, $3)", comment.artwork_id, in_reply_to_id, comment_id).execute(&self.0).await?;
|
||||
}
|
||||
Ok(comment_id)
|
||||
}
|
||||
|
||||
pub async fn read_all(&self) -> Result<Vec<Comment>> {
|
||||
// TODO: joins to get in_reply_to_ids and mentioned_by_ids
|
||||
let comments: Vec<Comment> = sqlx::query_as("select * from comments")
|
||||
.fetch_all(&self.0)
|
||||
.await?;
|
||||
Ok(comments)
|
||||
}
|
||||
|
||||
pub async fn read_thread(&self, artwork_id: i32) -> Result<Vec<Comment>> {
|
||||
Ok(sqlx::query_as("select * from comments")
|
||||
.fetch_all(&self.0)
|
||||
.await?)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,11 @@
|
|||
use artists::Artists;
|
||||
use artworks::Artworks;
|
||||
use comments::Comments;
|
||||
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
|
||||
|
||||
mod artists;
|
||||
mod artworks;
|
||||
mod comments;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Database(Pool<Postgres>);
|
||||
|
@ -17,4 +22,16 @@ impl Database {
|
|||
|
||||
Self(pool)
|
||||
}
|
||||
|
||||
pub fn artists(&self) -> Artists {
|
||||
Artists::new(self.0.clone())
|
||||
}
|
||||
|
||||
pub fn artworks(&self) -> Artworks {
|
||||
Artworks::new(self.0.clone())
|
||||
}
|
||||
|
||||
pub fn comments(&self) -> Comments {
|
||||
Comments::new(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
|
39
src/error.rs
39
src/error.rs
|
@ -1,22 +1,46 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use poem::{error::ResponseError, http::StatusCode};
|
||||
use sqlx::postgres::PgDatabaseError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
IOError(std::io::Error),
|
||||
TOMLError(toml::de::Error),
|
||||
SQLError(String),
|
||||
DatabaseError(sqlx::Error),
|
||||
NotFound,
|
||||
MissingField,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::SQLError(error) => write!(f, "SQL Error: {}", error),
|
||||
Error::IOError(error) => write!(f, "IO Error: {}", error),
|
||||
Error::TOMLError(error) => write!(f, "TOML deserialization error: {}", error),
|
||||
Error::DatabaseError(error) => write!(f, "database error: {}", error),
|
||||
Error::NotFound => write!(f, "not found"),
|
||||
Error::MissingField => write!(f, "missing field in row"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl ResponseError for Error {
|
||||
fn status(&self) -> poem::http::StatusCode {
|
||||
match self {
|
||||
Error::IOError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Error::TOMLError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Error::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Error::NotFound => StatusCode::NOT_FOUND,
|
||||
Error::SQLError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Error::MissingField => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Self::IOError(e)
|
||||
|
@ -28,3 +52,18 @@ impl From<toml::de::Error> for Error {
|
|||
Self::TOMLError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for Error {
|
||||
fn from(e: sqlx::Error) -> Self {
|
||||
match e {
|
||||
sqlx::Error::Database(database_error) => {
|
||||
let error = database_error.downcast::<PgDatabaseError>();
|
||||
match error.code() {
|
||||
code => Error::SQLError(code.to_string()),
|
||||
}
|
||||
}
|
||||
sqlx::Error::RowNotFound => Error::NotFound,
|
||||
_ => Self::DatabaseError(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
src/file.rs
30
src/file.rs
|
@ -2,6 +2,32 @@ use uuid::Uuid;
|
|||
|
||||
#[derive(sqlx::FromRow)]
|
||||
pub struct File {
|
||||
id: Option<Uuid>,
|
||||
artwork: usize,
|
||||
id: Uuid,
|
||||
pub alt_text: String,
|
||||
extension: String,
|
||||
artwork_id: Option<i32>,
|
||||
}
|
||||
|
||||
impl File {
|
||||
pub fn new(file: std::fs::File, extension: String) -> Self {
|
||||
let id = Uuid::new_v4();
|
||||
Self {
|
||||
id,
|
||||
alt_text: String::new(),
|
||||
extension,
|
||||
artwork_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn extension(&self) -> &str {
|
||||
&self.extension
|
||||
}
|
||||
|
||||
pub fn artwork_id(&self) -> Option<i32> {
|
||||
self.artwork_id
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,3 +26,5 @@ impl Critch {
|
|||
Self { db, config }
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -1,3 +1,5 @@
|
|||
use poem::session::{CookieConfig, CookieSession};
|
||||
use poem::web::cookie::CookieKey;
|
||||
use poem::{delete, post, put, EndpointExt};
|
||||
use poem::{get, listener::TcpListener, Route, Server};
|
||||
|
||||
|
@ -10,11 +12,16 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
let config = Config::from_file("./critch.toml").unwrap();
|
||||
let state = Critch::new(config).await;
|
||||
|
||||
let cookie_config = CookieConfig::private(CookieKey::generate());
|
||||
let cookie_session = CookieSession::new(cookie_config);
|
||||
|
||||
let app = Route::new()
|
||||
.at("/admin", get(routes::admin::get_dashboard))
|
||||
.at(
|
||||
"/admin",
|
||||
"/admin/login",
|
||||
post(routes::admin::login).get(routes::admin::get_login_form),
|
||||
)
|
||||
.at("/admin/logout", post(routes::admin::logout))
|
||||
.at("/", get(routes::artworks::get))
|
||||
.at(
|
||||
"/artworks",
|
||||
|
@ -40,7 +47,9 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
"/artworks/:artwork/comments",
|
||||
post(routes::artworks::comments::post).delete(routes::artworks::comments::delete),
|
||||
)
|
||||
.data(state);
|
||||
.catch_all_error(routes::error::error)
|
||||
.data(state)
|
||||
.with(cookie_session);
|
||||
|
||||
Server::new(TcpListener::bind("0.0.0.0:3000"))
|
||||
.run(app)
|
||||
|
|
|
@ -1,11 +1,50 @@
|
|||
use poem::handler;
|
||||
use poem::{
|
||||
handler,
|
||||
http::StatusCode,
|
||||
session::Session,
|
||||
web::{Data, Form, Redirect},
|
||||
IntoResponse, Response,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[handler]
|
||||
pub async fn login() {
|
||||
todo!()
|
||||
use crate::{ructe_poem::render, templates, Critch, Result};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Login {
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn get_login_form() {
|
||||
todo!()
|
||||
pub async fn get_dashboard(session: &Session, critch: Data<&Critch>) -> Result<Response> {
|
||||
if let Some(true) = session.get("is_admin") {
|
||||
let comments = critch.db.comments().read_all().await?;
|
||||
let artworks = critch.db.artworks().read_all().await?;
|
||||
return Ok(render!(templates::admin_dashboard_html).into_response());
|
||||
} else {
|
||||
return Ok(Redirect::see_other("/admin/login").into_response());
|
||||
}
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub fn login(session: &Session, data: Data<&Critch>, form: Form<Login>) -> Response {
|
||||
if form.password == data.config.admin_password() {
|
||||
session.set("is_admin", true);
|
||||
return Redirect::see_other("/admin").into_response();
|
||||
} else {
|
||||
return render!(templates::admin_login_html).into_response();
|
||||
}
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub fn logout(session: &Session) -> Response {
|
||||
session.purge();
|
||||
Redirect::see_other("/").into_response()
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub fn get_login_form(session: &Session) -> Response {
|
||||
if let Some(true) = session.get("is_admin") {
|
||||
return Redirect::see_other("/admin").into_response();
|
||||
};
|
||||
render!(templates::admin_login_html).into_response()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
use poem::{IntoResponse, Response};
|
||||
|
||||
use crate::{ructe_poem::render, templates};
|
||||
|
||||
pub async fn error(err: poem::Error) -> Response {
|
||||
let status = err.status().to_string();
|
||||
let message = err.to_string();
|
||||
render!(templates::error_html, &status, &message).into_response()
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod admin;
|
||||
pub mod artists;
|
||||
pub mod artworks;
|
||||
pub mod error;
|
||||
|
|
|
@ -2,11 +2,11 @@ use poem::{http::StatusCode, Body, IntoResponse};
|
|||
|
||||
macro_rules! render {
|
||||
($template:path) => {{
|
||||
use $crate::axum_ructe::Render;
|
||||
use $crate::ructe_poem::Render;
|
||||
Render(|o| $template(o))
|
||||
}};
|
||||
($template:path, $($arg:expr),* $(,)*) => {{
|
||||
use $crate::axum_ructe::Render;
|
||||
use $crate::ructe_poem::Render;
|
||||
Render(move |o| $template(o, $($arg),*))
|
||||
}}
|
||||
}
|
||||
|
@ -25,3 +25,5 @@ impl<T: FnOnce(&mut Vec<u8>) -> std::io::Result<()> + Send> IntoResponse for Ren
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use render;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
@use super::base_html;
|
||||
|
||||
@()
|
||||
|
||||
@:base_html({
|
||||
<form action="/admin/logout" method="post">
|
||||
<button type="logout">log out</button>
|
||||
</form>
|
||||
})
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
@use super::base_html;
|
||||
|
||||
@()
|
||||
|
||||
@:base_html({
|
||||
<form action="/admin/login" method="post">
|
||||
<label for="password">admin password:</label>
|
||||
<input type="text" id="password" name="password" required="true" />
|
||||
<button type="submit">log in</button>
|
||||
</form>
|
||||
})
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@use super::statics::*;
|
||||
|
||||
@(body: Content)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/style.css" />
|
||||
<title>pinussy</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@:body()
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
@use super::base_html;
|
||||
@use poem::http::StatusCode;
|
||||
|
||||
@(status: &str, message: &str)
|
||||
|
||||
@:base_html({
|
||||
<h1>error @status</h1>
|
||||
<h2>@message</h2>
|
||||
})
|
Loading…
Reference in New Issue