Create a projects board display
This commit is contained in:
parent
64e810d360
commit
38dbf10abe
6 changed files with 164 additions and 89 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
projects.db
|
||||||
|
|
|
||||||
46
Cargo.lock
generated
46
Cargo.lock
generated
|
|
@ -497,10 +497,33 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
name = "makeprogress"
|
name = "makeprogress"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"maud",
|
||||||
"rouille",
|
"rouille",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maud"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df518b75016b4289cdddffa1b01f2122f4a49802c93191f3133f6dc2472ebcaa"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"maud_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maud_macros"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa453238ec218da0af6b11fc5978d3b5c3a45ed97b722391a2a11f3306274e18"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.7.6"
|
||||||
|
|
@ -617,6 +640,29 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rouille = "3.6"
|
rouille = "3.6"
|
||||||
rusqlite = { version = "0.32", features = ["bundled"] }
|
rusqlite = { version = "0.32", features = ["bundled"] }
|
||||||
|
maud = "0.26"
|
||||||
|
|
|
||||||
173
src/db.rs
173
src/db.rs
|
|
@ -1,96 +1,93 @@
|
||||||
use crate::project::Project;
|
use crate::project::Project;
|
||||||
use rusqlite::{Connection, Result};
|
use rusqlite::{Connection, Result};
|
||||||
|
|
||||||
pub struct Database {
|
fn open_connection() -> Result<Connection> {
|
||||||
conn: Connection,
|
let conn = Connection::open("projects.db")?;
|
||||||
|
conn.execute_batch(include_str!("schema.sql"))?;
|
||||||
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
pub fn create_project(title: String) -> Result<i64> {
|
||||||
pub fn new(path: &str) -> Result<Self> {
|
let conn = open_connection()?;
|
||||||
let conn = Connection::open(path)?;
|
let now = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs() as i64;
|
||||||
|
|
||||||
conn.execute_batch(include_str!("schema.sql"))?;
|
conn.execute(
|
||||||
|
"INSERT INTO projects (title, created_time, last_modified_time, percentage_completed, archived)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
|
(&title, now, now, 0, 0),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(Self { conn })
|
Ok(conn.last_insert_rowid())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_project(&self, title: String) -> Result<i64> {
|
pub fn update_project_progress(id: i64, percentage: i32) -> Result<()> {
|
||||||
let now = std::time::SystemTime::now()
|
let conn = open_connection()?;
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
let now = std::time::SystemTime::now()
|
||||||
.unwrap()
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.as_secs() as i64;
|
.unwrap()
|
||||||
|
.as_secs() as i64;
|
||||||
self.conn.execute(
|
|
||||||
"INSERT INTO projects (title, created_time, last_modified_time, percentage_completed, archived)
|
conn.execute(
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5)",
|
"UPDATE projects SET percentage_completed = ?1, last_modified_time = ?2 WHERE id = ?3",
|
||||||
(&title, now, now, 0, 0),
|
(percentage, now, id),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(self.conn.last_insert_rowid())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_project_progress(&self, id: i64, percentage: i32) -> Result<()> {
|
pub fn archive_project(id: i64) -> Result<()> {
|
||||||
let now = std::time::SystemTime::now()
|
let conn = open_connection()?;
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
let now = std::time::SystemTime::now()
|
||||||
.unwrap()
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.as_secs() as i64;
|
.unwrap()
|
||||||
|
.as_secs() as i64;
|
||||||
self.conn.execute(
|
|
||||||
"UPDATE projects SET percentage_completed = ?1, last_modified_time = ?2 WHERE id = ?3",
|
conn.execute(
|
||||||
(percentage, now, id),
|
"UPDATE projects SET archived = 1, last_modified_time = ?1 WHERE id = ?2",
|
||||||
)?;
|
(now, id),
|
||||||
|
)?;
|
||||||
Ok(())
|
|
||||||
}
|
Ok(())
|
||||||
|
}
|
||||||
pub fn archive_project(&self, id: i64) -> Result<()> {
|
|
||||||
let now = std::time::SystemTime::now()
|
pub fn unarchive_project(id: i64) -> Result<()> {
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
let conn = open_connection()?;
|
||||||
.unwrap()
|
let now = std::time::SystemTime::now()
|
||||||
.as_secs() as i64;
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
self.conn.execute(
|
.as_secs() as i64;
|
||||||
"UPDATE projects SET archived = 1, last_modified_time = ?1 WHERE id = ?2",
|
|
||||||
(now, id),
|
conn.execute(
|
||||||
)?;
|
"UPDATE projects SET archived = 0, last_modified_time = ?1 WHERE id = ?2",
|
||||||
|
(now, id),
|
||||||
Ok(())
|
)?;
|
||||||
}
|
|
||||||
|
Ok(())
|
||||||
pub fn unarchive_project(&self, id: i64) -> Result<()> {
|
}
|
||||||
let now = std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
pub fn list_all_projects() -> Result<Vec<Project>> {
|
||||||
.unwrap()
|
let conn = open_connection()?;
|
||||||
.as_secs() as i64;
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT id, title, created_time, last_modified_time, percentage_completed, archived
|
||||||
self.conn.execute(
|
FROM projects
|
||||||
"UPDATE projects SET archived = 0, last_modified_time = ?1 WHERE id = ?2",
|
ORDER BY last_modified_time DESC",
|
||||||
(now, id),
|
)?;
|
||||||
)?;
|
|
||||||
|
let projects = stmt
|
||||||
Ok(())
|
.query_map([], |row| {
|
||||||
}
|
Ok(Project {
|
||||||
|
id: row.get(0)?,
|
||||||
pub fn list_all_projects(&self) -> Result<Vec<Project>> {
|
title: row.get(1)?,
|
||||||
let mut stmt = self.conn.prepare(
|
created_time: row.get(2)?,
|
||||||
"SELECT id, title, created_time, last_modified_time, percentage_completed, archived
|
last_modified_time: row.get(3)?,
|
||||||
FROM projects
|
percentage_completed: row.get(4)?,
|
||||||
ORDER BY last_modified_time DESC",
|
archived: row.get::<_, i32>(5)? != 0,
|
||||||
)?;
|
})
|
||||||
|
})?
|
||||||
let projects = stmt
|
.collect::<Result<Vec<_>>>()?;
|
||||||
.query_map([], |row| {
|
|
||||||
Ok(Project {
|
Ok(projects)
|
||||||
id: row.get(0)?,
|
|
||||||
title: row.get(1)?,
|
|
||||||
created_time: row.get(2)?,
|
|
||||||
last_modified_time: row.get(3)?,
|
|
||||||
percentage_completed: row.get(4)?,
|
|
||||||
archived: row.get::<_, i32>(5)? != 0,
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
Ok(projects)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
src/main.rs
30
src/main.rs
|
|
@ -1,3 +1,4 @@
|
||||||
|
use maud::{DOCTYPE, html};
|
||||||
use rouille::router;
|
use rouille::router;
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
|
|
@ -16,6 +17,35 @@ fn main() {
|
||||||
let js = include_bytes!("project-card.js");
|
let js = include_bytes!("project-card.js");
|
||||||
rouille::Response::from_data("application/javascript", js.as_ref())
|
rouille::Response::from_data("application/javascript", js.as_ref())
|
||||||
},
|
},
|
||||||
|
(GET) ["/projects"] => {
|
||||||
|
let projects = db::list_all_projects().unwrap_or_default();
|
||||||
|
|
||||||
|
let markup = html! {
|
||||||
|
(DOCTYPE)
|
||||||
|
html {
|
||||||
|
head {
|
||||||
|
meta charset="utf-8";
|
||||||
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||||
|
link rel="stylesheet" href="/main.css";
|
||||||
|
script src="/project-card.js" {}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
main {
|
||||||
|
section {
|
||||||
|
@for project in &projects {
|
||||||
|
project-card
|
||||||
|
title=(project.title)
|
||||||
|
percentage=(project.percentage_completed)
|
||||||
|
archived=(project.archived) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rouille::Response::html(markup.into_string())
|
||||||
|
},
|
||||||
_ => rouille::Response::empty_404()
|
_ => rouille::Response::empty_404()
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
2
todo.md
2
todo.md
|
|
@ -7,6 +7,6 @@
|
||||||
[x] Create a `main.css` file in src. Use mvp.css as a starting point.
|
[x] Create a `main.css` file in src. Use mvp.css as a starting point.
|
||||||
[x] Create a `project-card.js` file in src that creates a web component for displaying a project.
|
[x] Create a `project-card.js` file in src that creates a web component for displaying a project.
|
||||||
[x] Using a Rouille router, create `GET /main.css` and `GET /project-card.js` endpoints that returns the relevant files. Use the include_bytes! macro.
|
[x] Using a Rouille router, create `GET /main.css` and `GET /project-card.js` endpoints that returns the relevant files. Use the include_bytes! macro.
|
||||||
[ ] Create a `GET /projects` endpoint. Using Maud for markup generation, have this endpoint return an HTML page that shows all projects. Each project should be a `project-card` web component. Keep the page simple: no title or any buttons currently.
|
[x] Create a `GET /projects` endpoint. Using Maud for markup generation, have this endpoint return an HTML page that shows all projects. Each project should be a `project-card` web component. Keep the page simple: no title or any buttons currently.
|
||||||
[ ] Create a `POST /projects` endpoint that accepts URL encoded data and creates a new project.
|
[ ] Create a `POST /projects` endpoint that accepts URL encoded data and creates a new project.
|
||||||
[ ] Create a `GET /new-project` endpoint that returns a HTML page with a project creation form. Keep it simple.
|
[ ] Create a `GET /new-project` endpoint that returns a HTML page with a project creation form. Keep it simple.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue