This commit is contained in:
Mateusz Gruszczyński
2026-07-20 15:59:15 +02:00
parent 771494671b
commit dfa0828c38
42 changed files with 964 additions and 755 deletions
+151 -11
View File
@@ -27,6 +27,7 @@ pub struct Note {
pub content: String,
pub created_at: String,
pub updated_at: String,
pub owner_map: String,
}
#[derive(Debug, Serialize, FromRow)]
@@ -34,6 +35,8 @@ pub struct Revision {
pub id: i64,
pub content: String,
pub created_at: String,
pub author: Option<String>,
pub owner_map: String,
}
pub async fn find_workspace(pool: &SqlitePool, slug: &str) -> Result<Option<Workspace>, sqlx::Error> {
@@ -86,7 +89,7 @@ pub fn verify_workspace_password(workspace: &Workspace, password: Option<&str>)
pub async fn list_notes(pool: &SqlitePool, workspace_id: i64) -> Result<Vec<Note>, sqlx::Error> {
sqlx::query_as::<_, Note>(
"SELECT id, workspace_id, slug, title, content, created_at, updated_at FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC",
"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC",
)
.bind(workspace_id)
.fetch_all(pool)
@@ -99,7 +102,7 @@ pub async fn find_note(
slug: &str,
) -> Result<Option<Note>, sqlx::Error> {
sqlx::query_as::<_, Note>(
"SELECT id, workspace_id, slug, title, content, created_at, updated_at FROM notes WHERE workspace_id = ? AND slug = ?",
"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map FROM notes WHERE workspace_id = ? AND slug = ?",
)
.bind(workspace_id)
.bind(slug)
@@ -123,7 +126,7 @@ pub async fn create_note(
.await?;
sqlx::query_as::<_, Note>(
"SELECT id, workspace_id, slug, title, content, created_at, updated_at FROM notes WHERE id = ?",
"SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map FROM notes WHERE id = ?",
)
.bind(result.last_insert_rowid())
.fetch_one(pool)
@@ -135,10 +138,13 @@ pub async fn save_revision(
note_id: i64,
workspace_id: i64,
content: &str,
author: Option<&str>,
owner_map: &str,
) -> Result<(i64, String), sqlx::Error> {
let mut tx = pool.begin().await?;
sqlx::query("UPDATE notes SET content = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
sqlx::query("UPDATE notes SET content = ?, owner_map = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
.bind(content)
.bind(owner_map)
.bind(note_id)
.execute(&mut *tx)
.await?;
@@ -146,9 +152,11 @@ pub async fn save_revision(
.bind(workspace_id)
.execute(&mut *tx)
.await?;
let result = sqlx::query("INSERT INTO note_revisions (note_id, content) VALUES (?, ?)")
let result = sqlx::query("INSERT INTO note_revisions (note_id, content, author, owner_map) VALUES (?, ?, ?, ?)")
.bind(note_id)
.bind(content)
.bind(author)
.bind(owner_map)
.execute(&mut *tx)
.await?;
let updated_at: String = sqlx::query_scalar("SELECT updated_at FROM notes WHERE id = ?")
@@ -161,7 +169,7 @@ pub async fn save_revision(
pub async fn list_revisions(pool: &SqlitePool, note_id: i64) -> Result<Vec<Revision>, sqlx::Error> {
sqlx::query_as::<_, Revision>(
"SELECT id, content, created_at FROM note_revisions WHERE note_id = ? ORDER BY id DESC LIMIT 100",
"SELECT id, content, created_at, author, owner_map FROM note_revisions WHERE note_id = ? ORDER BY id DESC LIMIT 100",
)
.bind(note_id)
.fetch_all(pool)
@@ -202,11 +210,12 @@ pub struct Pad {
pub password_hash: Option<String>,
pub created_at: String,
pub updated_at: String,
pub owner_map: String,
}
pub async fn find_pad(pool: &SqlitePool, slug: &str) -> Result<Option<Pad>, sqlx::Error> {
sqlx::query_as::<_, Pad>(
"SELECT id, slug, title, content, password_hash, created_at, updated_at FROM pads WHERE slug = ?",
"SELECT id, slug, title, content, password_hash, created_at, updated_at, owner_map FROM pads WHERE slug = ?",
)
.bind(slug)
.fetch_optional(pool)
@@ -230,7 +239,7 @@ pub async fn create_pad(
.await?;
sqlx::query_as::<_, Pad>(
"SELECT id, slug, title, content, password_hash, created_at, updated_at FROM pads WHERE id = ?",
"SELECT id, slug, title, content, password_hash, created_at, updated_at, owner_map FROM pads WHERE id = ?",
)
.bind(result.last_insert_rowid())
.fetch_one(pool)
@@ -256,16 +265,21 @@ pub async fn save_pad_revision(
pool: &SqlitePool,
pad_id: i64,
content: &str,
author: Option<&str>,
owner_map: &str,
) -> Result<(i64, String), sqlx::Error> {
let mut tx = pool.begin().await?;
sqlx::query("UPDATE pads SET content = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
sqlx::query("UPDATE pads SET content = ?, owner_map = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
.bind(content)
.bind(owner_map)
.bind(pad_id)
.execute(&mut *tx)
.await?;
let result = sqlx::query("INSERT INTO revisions (pad_id, content) VALUES (?, ?)")
let result = sqlx::query("INSERT INTO revisions (pad_id, content, author, owner_map) VALUES (?, ?, ?, ?)")
.bind(pad_id)
.bind(content)
.bind(author)
.bind(owner_map)
.execute(&mut *tx)
.await?;
let updated_at: String = sqlx::query_scalar("SELECT updated_at FROM pads WHERE id = ?")
@@ -281,9 +295,135 @@ pub async fn list_pad_revisions(
pad_id: i64,
) -> Result<Vec<Revision>, sqlx::Error> {
sqlx::query_as::<_, Revision>(
"SELECT id, content, created_at FROM revisions WHERE pad_id = ? ORDER BY id DESC LIMIT 100",
"SELECT id, content, created_at, author, owner_map FROM revisions WHERE pad_id = ? ORDER BY id DESC LIMIT 100",
)
.bind(pad_id)
.fetch_all(pool)
.await
}
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct PublishedPage {
pub token: String,
pub title: String,
pub content: String,
pub updated_at: String,
}
pub async fn publish_pad(pool: &SqlitePool, pad_id: i64) -> Result<String, sqlx::Error> {
if let Some(token) = sqlx::query_scalar::<_, String>("SELECT token FROM published_pages WHERE pad_id = ?")
.bind(pad_id)
.fetch_optional(pool)
.await?
{
return Ok(token);
}
let token = random_suffix(18);
sqlx::query("INSERT INTO published_pages (token, pad_id) VALUES (?, ?)")
.bind(&token)
.bind(pad_id)
.execute(pool)
.await?;
Ok(token)
}
pub async fn publish_note(pool: &SqlitePool, note_id: i64) -> Result<String, sqlx::Error> {
if let Some(token) = sqlx::query_scalar::<_, String>("SELECT token FROM published_pages WHERE note_id = ?")
.bind(note_id)
.fetch_optional(pool)
.await?
{
return Ok(token);
}
let token = random_suffix(18);
sqlx::query("INSERT INTO published_pages (token, note_id) VALUES (?, ?)")
.bind(&token)
.bind(note_id)
.execute(pool)
.await?;
Ok(token)
}
pub async fn find_published_page(pool: &SqlitePool, token: &str) -> Result<Option<PublishedPage>, sqlx::Error> {
sqlx::query_as::<_, PublishedPage>(
"SELECT pp.token, COALESCE(p.title, n.title) AS title, COALESCE(p.content, n.content) AS content, COALESCE(p.updated_at, n.updated_at) AS updated_at FROM published_pages pp LEFT JOIN pads p ON p.id = pp.pad_id LEFT JOIN notes n ON n.id = pp.note_id WHERE pp.token = ?",
)
.bind(token)
.fetch_optional(pool)
.await
}
pub async fn pad_file_token(pool: &SqlitePool, pad_id: i64) -> Result<String, sqlx::Error> {
if let Some(token) = sqlx::query_scalar::<_, Option<String>>("SELECT file_token FROM pads WHERE id = ?")
.bind(pad_id)
.fetch_one(pool)
.await?
{
return Ok(token);
}
let token = format!("p_{}", random_suffix(24));
sqlx::query("UPDATE pads SET file_token = ? WHERE id = ? AND file_token IS NULL")
.bind(&token)
.bind(pad_id)
.execute(pool)
.await?;
sqlx::query_scalar::<_, String>("SELECT file_token FROM pads WHERE id = ?")
.bind(pad_id)
.fetch_one(pool)
.await
}
pub async fn note_file_token(pool: &SqlitePool, note_id: i64) -> Result<String, sqlx::Error> {
if let Some(token) = sqlx::query_scalar::<_, Option<String>>("SELECT file_token FROM notes WHERE id = ?")
.bind(note_id)
.fetch_one(pool)
.await?
{
return Ok(token);
}
let token = format!("n_{}", random_suffix(24));
sqlx::query("UPDATE notes SET file_token = ? WHERE id = ? AND file_token IS NULL")
.bind(&token)
.bind(note_id)
.execute(pool)
.await?;
sqlx::query_scalar::<_, String>("SELECT file_token FROM notes WHERE id = ?")
.bind(note_id)
.fetch_one(pool)
.await
}
#[derive(Debug, Clone, Copy)]
pub enum FileOwnerKind {
Pad,
Note,
}
#[derive(Debug, Clone, Copy)]
pub struct FileOwner {
pub kind: FileOwnerKind,
pub id: i64,
}
pub async fn find_file_owner(pool: &SqlitePool, token: &str) -> Result<Option<FileOwner>, sqlx::Error> {
if let Some(id) = sqlx::query_scalar::<_, i64>("SELECT id FROM pads WHERE file_token = ?")
.bind(token)
.fetch_optional(pool)
.await?
{
return Ok(Some(FileOwner { kind: FileOwnerKind::Pad, id }));
}
if let Some(id) = sqlx::query_scalar::<_, i64>("SELECT id FROM notes WHERE file_token = ?")
.bind(token)
.fetch_optional(pool)
.await?
{
return Ok(Some(FileOwner { kind: FileOwnerKind::Note, id }));
}
Ok(None)
}