.
This commit is contained in:
parent
8bede90019
commit
142129d6eb
|
|
@ -3,8 +3,14 @@ name = "atelier"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [".", "newt"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rusqlite = { version = "*" }
|
rusqlite = { version = "*" }
|
||||||
async-channel = { version = "*" }
|
async-channel = { version = "*" }
|
||||||
urlpattern = { version = "*" }
|
urlpattern = { version = "*" }
|
||||||
url = { version = "*" }
|
url = { version = "*" }
|
||||||
|
|
||||||
|
newt = { path = "./newt" }
|
||||||
7
rs/newt/Cargo.lock
generated
Normal file
7
rs/newt/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "newt"
|
||||||
|
version = "0.0.0"
|
||||||
18
rs/newt/Cargo.toml
Normal file
18
rs/newt/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "newt"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
# members = ["derive", "derive/core"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
proc-macro-error = "1.0"
|
||||||
|
proc-macro2 = "1"
|
||||||
|
syn = { version = "2", features = ["full"] }
|
||||||
|
quote = "1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# newt-derive = { path = "derive", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# derive = ["dep:newt-derive"]
|
||||||
22
rs/newt/src/builtin_impls.rs
Normal file
22
rs/newt/src/builtin_impls.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::prelude_internal::*;
|
||||||
|
|
||||||
|
impl Value for () {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value for &str {
|
||||||
|
fn render(&self, fmt: &mut dyn Write) -> FmtResult {
|
||||||
|
write!(fmt, "{self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Value for [(&str, &dyn Value); N] {
|
||||||
|
fn lookup(&self, name: &str) -> Option<&dyn Value> {
|
||||||
|
for (k, v) in self.iter() {
|
||||||
|
if *k == name {
|
||||||
|
return Some(*v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
90
rs/newt/src/lib.rs
Normal file
90
rs/newt/src/lib.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
mod builtin_impls;
|
||||||
|
pub mod parse;
|
||||||
|
pub mod prelude;
|
||||||
|
mod prelude_internal;
|
||||||
|
|
||||||
|
use crate::prelude_internal::*;
|
||||||
|
|
||||||
|
pub trait Value {
|
||||||
|
fn render(&self, w: &mut dyn Write) -> FmtResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(&self, name: &str) -> Option<&dyn Value> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Template {
|
||||||
|
commands: Vec<Command>,
|
||||||
|
}
|
||||||
|
impl Template {
|
||||||
|
pub fn render(&self, env: &dyn Value, target: &mut dyn Write) {
|
||||||
|
for command in self.commands.iter() {
|
||||||
|
match command {
|
||||||
|
Command::Print(s) => {
|
||||||
|
let _ = write!(target, "{s}");
|
||||||
|
},
|
||||||
|
Command::LookupPrint(s) => {
|
||||||
|
if let Some(value) = env.lookup(s) {
|
||||||
|
value.render(target);
|
||||||
|
} else {
|
||||||
|
let _ = write!(target, "<<<'{s}' not found>>>");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Command {
|
||||||
|
Print(String),
|
||||||
|
LookupPrint(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(input: &str) -> Template {
|
||||||
|
let mut state = parse::ParseState::new(input);
|
||||||
|
let mut commands = vec![];
|
||||||
|
|
||||||
|
state.parse_root(&mut commands);
|
||||||
|
|
||||||
|
Template { commands }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(input: &str, env: &dyn Value) -> String {
|
||||||
|
let template = parse(input);
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
template.render(env, &mut output);
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! env {
|
||||||
|
($($name:literal : $value:expr),* $(,)?) => {
|
||||||
|
&[$(($name, &$value as &dyn crate::Value),)*]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
macro_rules! go {
|
||||||
|
(($input:expr, $env:expr), $output:expr) => {
|
||||||
|
assert_eq!(crate::render($input, $env), $output)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple() {
|
||||||
|
go!(("Hello, World!", &()), "Hello, World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lookup() {
|
||||||
|
go!(
|
||||||
|
("Hello, {name}!", env! { "name": "Steve" }),
|
||||||
|
"Hello, Steve!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
rs/newt/src/parse.rs
Normal file
78
rs/newt/src/parse.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
use crate::prelude_internal::*;
|
||||||
|
|
||||||
|
pub struct ParseState<'a> {
|
||||||
|
input: &'a str,
|
||||||
|
at: usize,
|
||||||
|
}
|
||||||
|
impl<'a> ParseState<'a> {
|
||||||
|
pub fn new(input: &'a str) -> Self {
|
||||||
|
Self { input, at: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.head().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn head(&self) -> &str {
|
||||||
|
&self.input[self.at..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn goto(&mut self, at: usize) {
|
||||||
|
self.at = at;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip(&mut self, amt: usize) {
|
||||||
|
self.at += amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back(&mut self, amt: usize) {
|
||||||
|
self.at -= amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_root(&mut self, out: &mut Vec<Command>) {
|
||||||
|
while !self.is_empty() {
|
||||||
|
let head = self.head();
|
||||||
|
if let Some(bo) = head.find('{') {
|
||||||
|
if bo != 0 {
|
||||||
|
out.push(Command::Print(head[0..bo].to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.goto(bo);
|
||||||
|
self.parse_directive(out);
|
||||||
|
} else {
|
||||||
|
out.push(Command::Print(head.to_string()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_directive(&mut self, out: &mut Vec<Command>) {
|
||||||
|
assert!(self.head().starts_with('{'));
|
||||||
|
|
||||||
|
let mut ok = Err(self.at);
|
||||||
|
|
||||||
|
'inner: {
|
||||||
|
self.skip(1);
|
||||||
|
let head = self.head();
|
||||||
|
if let Some(bo) = head.find('}') {
|
||||||
|
let inner = &head[..bo];
|
||||||
|
let bo = bo + 1;
|
||||||
|
|
||||||
|
let mut inner = inner.split(' ').collect::<Vec<_>>();
|
||||||
|
match inner.as_slice() {
|
||||||
|
&[name] => {
|
||||||
|
out.push(Command::LookupPrint(name.to_string()));
|
||||||
|
ok = Ok(bo);
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match ok {
|
||||||
|
Err(checkpoint) => self.goto(checkpoint),
|
||||||
|
Ok(skip) => self.skip(skip),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
rs/newt/src/prelude.rs
Normal file
1
rs/newt/src/prelude.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub use crate::{Command, Template, Value};
|
||||||
3
rs/newt/src/prelude_internal.rs
Normal file
3
rs/newt/src/prelude_internal.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub use crate::prelude::*;
|
||||||
|
|
||||||
|
pub use std::fmt::{Display, Formatter, Result as FmtResult, Write};
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
|
pub mod prelude;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod rusqlite_thread_pool;
|
pub mod rusqlite_thread_pool;
|
||||||
|
|
||||||
|
|
|
||||||
1
rs/src/prelude.rs
Normal file
1
rs/src/prelude.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub use core::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
|
@ -37,7 +37,10 @@ impl<T> Router<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(&mut self, method: String, pathname: String, value: T) {
|
pub fn register(&mut self, method: &str, pathname: &str, value: T) {
|
||||||
|
let pathname = pathname.to_string();
|
||||||
|
let method = method.to_string();
|
||||||
|
|
||||||
let upwi = UrlPatternWithInput {
|
let upwi = UrlPatternWithInput {
|
||||||
pathname: pathname.clone(),
|
pathname: pathname.clone(),
|
||||||
pattern: UrlPattern::parse(
|
pattern: UrlPattern::parse(
|
||||||
|
|
@ -90,3 +93,9 @@ impl<T> Router<T> {
|
||||||
Err(404)
|
Err(404)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod methods {
|
||||||
|
pub const GET: &'static str = "GET";
|
||||||
|
pub const POST: &'static str = "POST";
|
||||||
|
pub const PUT: &'static str = "PUT";
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ hard_tabs = true
|
||||||
match_block_trailing_comma = true
|
match_block_trailing_comma = true
|
||||||
max_width = 80
|
max_width = 80
|
||||||
empty_item_single_line = false
|
empty_item_single_line = false
|
||||||
|
format_strings = true
|
||||||
|
|
|
||||||
5
shelves/Cargo.lock
generated
5
shelves/Cargo.lock
generated
|
|
@ -43,6 +43,7 @@ name = "atelier"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
"newt",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
|
|
@ -586,6 +587,10 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "newt"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,15 @@
|
||||||
|
mod prelude;
|
||||||
|
use prelude::*;
|
||||||
|
mod routes;
|
||||||
|
mod templates;
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::handler::HandlerWithoutStateExt;
|
use axum::handler::HandlerWithoutStateExt;
|
||||||
use axum::response::IntoResponse;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
pub type AnyError = Box<dyn Error + Send + Sync + 'static>;
|
|
||||||
pub type Result<T> = core::result::Result<T, AnyError>;
|
|
||||||
|
|
||||||
pub type Dbs = atelier::rusqlite_thread_pool::PoolSender;
|
|
||||||
pub struct RequestCtx {
|
|
||||||
dbs: Dbs,
|
|
||||||
path_params: atelier::router::PathParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn migrate(connection: &mut rusqlite::Connection) -> Result<()> {
|
fn migrate(connection: &mut rusqlite::Connection) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -24,58 +18,7 @@ fn init(connection: &mut rusqlite::Connection) -> rusqlite::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
type Request = axum::extract::Request;
|
async fn serve(dbs: DbS, router: Arc<Router>, req: Request) -> Response {
|
||||||
type Response = axum::response::Response;
|
|
||||||
type Handler = Box<
|
|
||||||
dyn Fn(
|
|
||||||
Request,
|
|
||||||
RequestCtx,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Response> + Send + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
type Router = atelier::router::Router<Handler>;
|
|
||||||
|
|
||||||
fn make_handler<Fut, F>(f: F) -> Handler
|
|
||||||
where
|
|
||||||
Fut: Future<Output = Response> + Send + 'static,
|
|
||||||
F: FnMut(Request, RequestCtx) -> Fut + Clone + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
Box::new(move |req, ctx| {
|
|
||||||
let mut f = f.clone();
|
|
||||||
Box::pin(async move { f(req, ctx).await })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_hello(req: Request, ctx: RequestCtx) -> Response {
|
|
||||||
"hello".into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_hello_name(req: Request, ctx: RequestCtx) -> Response {
|
|
||||||
format!("Hello, {}", ctx.path_params.get("name")).into_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_router() -> Router {
|
|
||||||
let mut router = atelier::router::Router::new();
|
|
||||||
|
|
||||||
macro_rules! r {
|
|
||||||
($m:expr, $p:expr, $h:expr) => {
|
|
||||||
router.register($m.to_string(), $p.to_string(), make_handler($h))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
r!("GET", "/hello", get_hello);
|
|
||||||
r!("GET", "/hello/:name", get_hello_name);
|
|
||||||
|
|
||||||
router
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serve(
|
|
||||||
dbs: Dbs,
|
|
||||||
router: Arc<Router>,
|
|
||||||
req: axum::extract::Request,
|
|
||||||
) -> axum::response::Response {
|
|
||||||
let method = req.method().as_str();
|
let method = req.method().as_str();
|
||||||
let uri = format!("{}", req.uri());
|
let uri = format!("{}", req.uri());
|
||||||
|
|
||||||
|
|
@ -104,7 +47,7 @@ async fn go() -> Result<()> {
|
||||||
let addr = "127.0.0.1:8333".parse::<SocketAddr>()?;
|
let addr = "127.0.0.1:8333".parse::<SocketAddr>()?;
|
||||||
let listener = TcpListener::bind(addr).await?;
|
let listener = TcpListener::bind(addr).await?;
|
||||||
|
|
||||||
let router = Arc::new(make_router());
|
let router = Arc::new(routes::make_router());
|
||||||
let s = move |req: Request| {
|
let s = move |req: Request| {
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let router = router.clone();
|
let router = router.clone();
|
||||||
|
|
|
||||||
48
shelves/backend/prelude.rs
Normal file
48
shelves/backend/prelude.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub use atelier::rusqlite_thread_pool::PoolSender as DbS;
|
||||||
|
pub type Request = axum::extract::Request;
|
||||||
|
pub struct RequestCtx {
|
||||||
|
pub dbs: DbS,
|
||||||
|
pub path_params: atelier::router::PathParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Response = axum::response::Response;
|
||||||
|
pub type Handler = Box<
|
||||||
|
dyn Fn(
|
||||||
|
Request,
|
||||||
|
RequestCtx,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Response> + Send + 'static>>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
>;
|
||||||
|
pub type Router = atelier::router::Router<Handler>;
|
||||||
|
pub type AnyError = Box<dyn Error + Send + Sync + 'static>;
|
||||||
|
pub type Result<T> = core::result::Result<T, AnyError>;
|
||||||
|
|
||||||
|
pub use axum::response::IntoResponse;
|
||||||
|
pub enum HandlerError {
|
||||||
|
InternalServerError(AnyError),
|
||||||
|
}
|
||||||
|
impl<E> From<E> for HandlerError
|
||||||
|
where
|
||||||
|
E: Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn from(v: E) -> Self {
|
||||||
|
Self::InternalServerError(Box::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl IntoResponse for HandlerError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
"".into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type HandlerResult<T: IntoResponse> = core::result::Result<T, HandlerError>;
|
||||||
|
|
||||||
|
pub use crate::templates::{template_fn, Template, TemplateFn};
|
||||||
|
pub use core::fmt::{Display, Formatter};
|
||||||
|
pub type FmtResult = core::fmt::Result;
|
||||||
|
pub use axum::response::Html;
|
||||||
17
shelves/backend/routes/hello.rs
Normal file
17
shelves/backend/routes/hello.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
fn Hello(name: &str) -> impl Template {
|
||||||
|
template_fn(move |f| write!(f, "Hello, {name}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view(
|
||||||
|
request: Request,
|
||||||
|
ctx: RequestCtx,
|
||||||
|
) -> HandlerResult<impl IntoResponse> {
|
||||||
|
let name = ctx.path_params.get("name");
|
||||||
|
|
||||||
|
let template = Hello(name).display();
|
||||||
|
let output = format!("{template}");
|
||||||
|
|
||||||
|
Ok(Html(output))
|
||||||
|
}
|
||||||
81
shelves/backend/routes/home.rs
Normal file
81
shelves/backend/routes/home.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
enum ActivityType {
|
||||||
|
CreatedItem,
|
||||||
|
}
|
||||||
|
struct Activity {
|
||||||
|
activity_type: ActivityType,
|
||||||
|
item_xid: String,
|
||||||
|
created_timestamp: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
||||||
|
let activities: Vec<_> = ctx
|
||||||
|
.dbs
|
||||||
|
.send(|conn| {
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
r#"
|
||||||
|
select activity_json_blob from activity
|
||||||
|
order by json_extract(activity_json_blob, '$.created_timestamp')
|
||||||
|
limit 20
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(stmt.query_map([], |r| r.get::<_, String>(0))?.collect())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
/*
|
||||||
|
db.prepare(`
|
||||||
|
`).values<[string]>().map(([blob]) => JSON.parse(blob));
|
||||||
|
|
||||||
|
const body = `
|
||||||
|
<h1>Recent Activity</h1>
|
||||||
|
${ActivityList({ activities: recentActivities })}
|
||||||
|
`;
|
||||||
|
return html(View({ title: 'Home', body }));
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
import { View } from '@/backend/templates/index.ts';
|
||||||
|
import { html } from '@atelier/responses.ts';
|
||||||
|
import { arrayIsEmpty } from '@atelier/array.ts';
|
||||||
|
|
||||||
|
type ActivityType = 'created_item';
|
||||||
|
type Activity = {
|
||||||
|
activityType: ActivityType;
|
||||||
|
itemXid: string;
|
||||||
|
createdTimestamp: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Activity(props: { activity: Activity }) {}
|
||||||
|
function ActivityList(props: { activities: Activity[] }) {
|
||||||
|
if (arrayIsEmpty(props.activities)) {
|
||||||
|
return 'No activities yet!';
|
||||||
|
}
|
||||||
|
|
||||||
|
const activities = props.activities.map((activity) => Activity({ activity }));
|
||||||
|
return `
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function viewHome(req: Request, { db }: RequestCtx) {
|
||||||
|
const recentActivities: Activity[] = db.prepare(`
|
||||||
|
select activity_json_blob from activity
|
||||||
|
order by json_extract(activity_json_blob, '$.createdTimestamp')
|
||||||
|
limit 20
|
||||||
|
`).values<[string]>().map(([blob]) => JSON.parse(blob));
|
||||||
|
|
||||||
|
const body = `
|
||||||
|
<h1>Recent Activity</h1>
|
||||||
|
${ActivityList({ activities: recentActivities })}
|
||||||
|
`;
|
||||||
|
return html(View({ title: 'Home', body }));
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
23
shelves/backend/routes/items.rs
Normal file
23
shelves/backend/routes/items.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub async fn view(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view_one(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view_create(
|
||||||
|
req: Request,
|
||||||
|
ctx: RequestCtx,
|
||||||
|
) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post_create(
|
||||||
|
req: Request,
|
||||||
|
ctx: RequestCtx,
|
||||||
|
) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
50
shelves/backend/routes/mod.rs
Normal file
50
shelves/backend/routes/mod.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
mod hello;
|
||||||
|
mod home;
|
||||||
|
mod items;
|
||||||
|
mod shelves;
|
||||||
|
|
||||||
|
use atelier::router::methods::*;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
fn make_handler<Fut, F, R>(f: F) -> Handler
|
||||||
|
where
|
||||||
|
Fut: Future<Output = R> + Send + 'static,
|
||||||
|
F: FnMut(Request, RequestCtx) -> Fut + Clone + Send + Sync + 'static,
|
||||||
|
R: IntoResponse,
|
||||||
|
{
|
||||||
|
Box::new(move |req, ctx| {
|
||||||
|
let mut f = f.clone();
|
||||||
|
Box::pin(async move { f(req, ctx).await.into_response() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_router() -> Router {
|
||||||
|
let mut router = atelier::router::Router::new();
|
||||||
|
|
||||||
|
macro_rules! r {
|
||||||
|
($m:expr, $p:expr, $h:expr) => {
|
||||||
|
router.register($m, $p, make_handler($h))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
r!(GET, "/", home::view);
|
||||||
|
r!(GET, "/shelves", shelves::view);
|
||||||
|
r!(GET, "/shelves/:shelf_xid", shelves::view_one);
|
||||||
|
r!(GET, "/items", items::view);
|
||||||
|
r!(GET, "/items/create", items::view_create);
|
||||||
|
r!(GET, "/items/:item_xid", items::view_one);
|
||||||
|
r!(POST, "/items/create", items::post_create);
|
||||||
|
r!(GET, "/hello/:name", hello::view);
|
||||||
|
|
||||||
|
r!(GET, "/static/*", serve_static);
|
||||||
|
|
||||||
|
router
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve_static(req: Request, ctx: RequestCtx) -> Response {
|
||||||
|
let url = req.uri().path();
|
||||||
|
|
||||||
|
"".into_response()
|
||||||
|
}
|
||||||
16
shelves/backend/routes/shelves.rs
Normal file
16
shelves/backend/routes/shelves.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub async fn view(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view_one(req: Request, ctx: RequestCtx) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view_create(
|
||||||
|
req: Request,
|
||||||
|
ctx: RequestCtx,
|
||||||
|
) -> HandlerResult<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
35
shelves/backend/templates/mod.rs
Normal file
35
shelves/backend/templates/mod.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub trait Template {
|
||||||
|
fn render(&self, fmt: &mut Formatter) -> FmtResult;
|
||||||
|
|
||||||
|
fn display(self) -> impl Display
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
struct D<T>(T);
|
||||||
|
impl<T: Template> Display for D<T> {
|
||||||
|
fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
|
||||||
|
self.0.render(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
D(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TemplateFn<F>(F);
|
||||||
|
pub fn template_fn<F>(f: F) -> TemplateFn<F>
|
||||||
|
where
|
||||||
|
F: Fn(&mut Formatter) -> FmtResult,
|
||||||
|
{
|
||||||
|
TemplateFn(f)
|
||||||
|
}
|
||||||
|
impl<F> Template for TemplateFn<F>
|
||||||
|
where
|
||||||
|
F: Fn(&mut Formatter) -> FmtResult,
|
||||||
|
{
|
||||||
|
fn render(&self, fmt: &mut Formatter) -> FmtResult {
|
||||||
|
self.0(fmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue