update rustpad

This commit is contained in:
Mateusz Gruszczyński
2026-07-27 16:25:49 +02:00
parent fafd240619
commit 4468011429
7 changed files with 114 additions and 164 deletions
Generated
+1 -1
View File
@@ -2581,7 +2581,7 @@ dependencies = [
[[package]]
name = "rustpad"
version = "0.1.6"
version = "0.1.7"
dependencies = [
"argon2",
"aws-config",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rustpad"
version = "0.1.6"
version = "0.1.7"
edition = "2024"
rust-version = "1.94"
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
+30 -3
View File
@@ -20,7 +20,7 @@ use lettre::{
use rand_core::{OsRng, RngCore};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use sqlx::FromRow;
use sqlx::{any::AnyRow, FromRow, Row};
use tracing::{debug, info, warn};
use crate::{
@@ -32,7 +32,7 @@ const MIN_PASSWORD: usize = 8;
const MAX_PASSWORD: usize = 128;
const MAX_NICKNAME: usize = 40;
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone)]
pub struct User {
pub id: i64,
pub nickname: String,
@@ -103,7 +103,7 @@ pub struct ResourceActionRequest {
#[serde(default)]
password: Option<String>,
}
#[derive(Serialize, FromRow)]
#[derive(Serialize)]
pub struct ResourceItem {
slug: String,
title: String,
@@ -117,6 +117,33 @@ pub struct ResourceItem {
shared_by: String,
}
impl<'r> sqlx::FromRow<'r, AnyRow> for User {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
Ok(Self {
id: row.try_get("id")?,
nickname: crate::row_decode::text(row, "nickname")?,
email: crate::row_decode::text(row, "email")?,
password_hash: crate::row_decode::text(row, "password_hash")?,
confirmed_at: crate::row_decode::optional_text(row, "confirmed_at")?,
})
}
}
impl<'r> sqlx::FromRow<'r, AnyRow> for ResourceItem {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
Ok(Self {
slug: crate::row_decode::text(row, "slug")?,
title: crate::row_decode::text(row, "title")?,
protected: row.try_get("protected")?,
updated_at: crate::row_decode::text(row, "updated_at")?,
private_resource: row.try_get("private")?,
owned: row.try_get("owned")?,
permission: crate::row_decode::text(row, "permission")?,
shared_by: crate::row_decode::text(row, "shared_by")?,
})
}
}
#[derive(Deserialize)]
pub struct PrivacyRequest {
kind: String,
+40 -152
View File
@@ -7,32 +7,7 @@ use chrono::{DateTime, NaiveDateTime, Utc};
use rand_core::{OsRng, RngCore};
use serde::Serialize;
use sqlx::FromRow;
use sqlx::{Any, Row, Transaction, any::AnyRow};
fn any_text(row: &AnyRow, column: &str) -> Result<String, sqlx::Error> {
match row.try_get::<String, _>(column) {
Ok(value) => Ok(value),
Err(string_error) => match row.try_get::<Vec<u8>, _>(column) {
Ok(value) => {
String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error)))
}
Err(_) => Err(string_error),
},
}
}
fn any_optional_text(row: &AnyRow, column: &str) -> Result<Option<String>, sqlx::Error> {
match row.try_get::<Option<String>, _>(column) {
Ok(value) => Ok(value),
Err(string_error) => match row.try_get::<Option<Vec<u8>>, _>(column) {
Ok(Some(value)) => String::from_utf8(value)
.map(Some)
.map_err(|error| sqlx::Error::Decode(Box::new(error))),
Ok(None) => Ok(None),
Err(_) => Err(string_error),
},
}
}
use sqlx::{any::AnyRow, Any, Row, Transaction};
async fn inserted_id(
kind: DatabaseKind,
@@ -51,7 +26,7 @@ async fn inserted_id(
sqlx::query_scalar(query).fetch_one(&mut **tx).await
}
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone)]
pub struct Workspace {
pub id: i64,
pub slug: String,
@@ -62,19 +37,7 @@ pub struct Workspace {
pub is_private: i64,
}
fn workspace_from_any_row(row: AnyRow) -> Result<Workspace, sqlx::Error> {
Ok(Workspace {
id: row.try_get("id")?,
slug: any_text(&row, "slug")?,
title: any_text(&row, "title")?,
password_hash: any_optional_text(&row, "password_hash")?,
created_at: any_text(&row, "created_at")?,
updated_at: any_text(&row, "updated_at")?,
is_private: row.try_get("is_private")?,
})
}
#[derive(Debug, Clone, Serialize, FromRow)]
#[derive(Debug, Clone, Serialize)]
pub struct Note {
pub id: i64,
#[serde(skip_serializing)]
@@ -122,22 +85,7 @@ impl From<SqliteNote> for Note {
}
}
fn note_from_any_row(row: AnyRow) -> Result<Note, sqlx::Error> {
Ok(Note {
id: row.try_get("id")?,
_workspace_id: row.try_get("workspace_id")?,
slug: any_text(&row, "slug")?,
title: any_text(&row, "title")?,
content: any_text(&row, "content")?,
created_at: any_text(&row, "created_at")?,
updated_at: any_text(&row, "updated_at")?,
owner_map: any_text(&row, "owner_map")?,
protected: row.try_get("protected")?,
created_by: any_optional_text(&row, "created_by")?,
})
}
#[derive(Debug, Serialize, FromRow)]
#[derive(Debug, Serialize)]
pub struct Revision {
pub id: i64,
pub content: String,
@@ -146,25 +94,7 @@ pub struct Revision {
pub owner_map: String,
}
fn revision_from_any_row(row: AnyRow) -> Result<Revision, sqlx::Error> {
Ok(Revision {
id: row.try_get("id")?,
content: any_text(&row, "content")?,
created_at: any_text(&row, "created_at")?,
author: any_optional_text(&row, "author")?,
owner_map: any_text(&row, "owner_map")?,
})
}
pub async fn find_workspace(pool: &Database, slug: &str) -> Result<Option<Workspace>, sqlx::Error> {
if pool.kind() == DatabaseKind::MySql {
return sqlx::query(queries::get(pool.kind(), queries::Q001))
.bind(slug)
.fetch_optional(pool.pool())
.await?
.map(workspace_from_any_row)
.transpose();
}
sqlx::query_as::<_, Workspace>(queries::get(pool.kind(), queries::Q001))
.bind(slug)
.fetch_optional(pool.pool())
@@ -187,14 +117,6 @@ pub async fn create_workspace(
.execute(pool.pool())
.await?;
if pool.kind() == DatabaseKind::MySql {
return workspace_from_any_row(
sqlx::query(queries::get(pool.kind(), queries::Q001))
.bind(slug)
.fetch_one(pool.pool())
.await?,
);
}
sqlx::query_as::<_, Workspace>(queries::get(pool.kind(), queries::Q001))
.bind(slug)
.fetch_one(pool.pool())
@@ -229,15 +151,6 @@ pub async fn list_notes(pool: &Database, workspace_id: i64) -> Result<Vec<Note>,
.map(Note::from)
.collect());
}
if pool.kind() == DatabaseKind::MySql {
return sqlx::query(queries::get(pool.kind(), queries::Q003))
.bind(workspace_id)
.fetch_all(pool.pool())
.await?
.into_iter()
.map(note_from_any_row)
.collect();
}
sqlx::query_as::<_, Note>(queries::get(pool.kind(), queries::Q003))
.bind(workspace_id)
.fetch_all(pool.pool())
@@ -257,15 +170,6 @@ pub async fn find_note(
.await?
.map(Note::from));
}
if pool.kind() == DatabaseKind::MySql {
return sqlx::query(queries::get(pool.kind(), queries::Q004))
.bind(workspace_id)
.bind(slug)
.fetch_optional(pool.pool())
.await?
.map(note_from_any_row)
.transpose();
}
sqlx::query_as::<_, Note>(queries::get(pool.kind(), queries::Q004))
.bind(workspace_id)
.bind(slug)
@@ -331,15 +235,6 @@ pub async fn save_revision(
}
pub async fn list_revisions(pool: &Database, note_id: i64) -> Result<Vec<Revision>, sqlx::Error> {
if pool.kind() == DatabaseKind::MySql {
return sqlx::query(queries::get(pool.kind(), queries::Q010))
.bind(note_id)
.fetch_all(pool.pool())
.await?
.into_iter()
.map(revision_from_any_row)
.collect();
}
sqlx::query_as::<_, Revision>(queries::get(pool.kind(), queries::Q010))
.bind(note_id)
.fetch_all(pool.pool())
@@ -406,7 +301,7 @@ pub fn normalize_timestamp(value: &str) -> String {
value.to_string()
}
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone)]
pub struct Pad {
pub id: i64,
pub slug: String,
@@ -419,29 +314,7 @@ pub struct Pad {
pub is_private: i64,
}
fn pad_from_any_row(row: AnyRow) -> Result<Pad, sqlx::Error> {
Ok(Pad {
id: row.try_get("id")?,
slug: any_text(&row, "slug")?,
title: any_text(&row, "title")?,
content: any_text(&row, "content")?,
password_hash: any_optional_text(&row, "password_hash")?,
created_at: any_text(&row, "created_at")?,
updated_at: any_text(&row, "updated_at")?,
owner_map: any_text(&row, "owner_map")?,
is_private: row.try_get("is_private")?,
})
}
pub async fn find_pad(pool: &Database, slug: &str) -> Result<Option<Pad>, sqlx::Error> {
if pool.kind() == DatabaseKind::MySql {
return sqlx::query(queries::get(pool.kind(), queries::Q011))
.bind(slug)
.fetch_optional(pool.pool())
.await?
.map(pad_from_any_row)
.transpose();
}
sqlx::query_as::<_, Pad>(queries::get(pool.kind(), queries::Q011))
.bind(slug)
.fetch_optional(pool.pool())
@@ -464,14 +337,6 @@ pub async fn create_pad(
.execute(pool.pool())
.await?;
if pool.kind() == DatabaseKind::MySql {
return pad_from_any_row(
sqlx::query(queries::get(pool.kind(), queries::Q011))
.bind(slug)
.fetch_one(pool.pool())
.await?,
);
}
sqlx::query_as::<_, Pad>(queries::get(pool.kind(), queries::Q011))
.bind(slug)
.fetch_one(pool.pool())
@@ -530,15 +395,6 @@ pub async fn list_pad_revisions(
pool: &Database,
pad_id: i64,
) -> Result<Vec<Revision>, sqlx::Error> {
if pool.kind() == DatabaseKind::MySql {
return sqlx::query(queries::get(pool.kind(), queries::Q016))
.bind(pad_id)
.fetch_all(pool.pool())
.await?
.into_iter()
.map(revision_from_any_row)
.collect();
}
sqlx::query_as::<_, Revision>(queries::get(pool.kind(), queries::Q016))
.bind(pad_id)
.fetch_all(pool.pool())
@@ -556,7 +412,7 @@ pub struct PublishedPage {
pub updated_at: String,
}
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone)]
struct PublishedPageRow {
token: String,
pad_id: Option<i64>,
@@ -567,7 +423,7 @@ struct PublishedPageRow {
updated_at: String,
}
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone)]
struct PostgresPublishedPageRow {
token: String,
pad_id: Option<i64>,
@@ -863,7 +719,7 @@ pub async fn find_file_owner(
Ok(None)
}
#[derive(Debug, Clone, Serialize, FromRow)]
#[derive(Debug, Clone, Serialize)]
pub struct NoteFile {
pub id: i64,
pub filename: String,
@@ -1079,3 +935,35 @@ pub async fn delete_pad_file(
.await?;
Ok(())
}
impl<'r> sqlx::FromRow<'r, AnyRow> for Workspace {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
Ok(Self { id: row.try_get("id")?, slug: crate::row_decode::text(row, "slug")?, title: crate::row_decode::text(row, "title")?, password_hash: crate::row_decode::optional_text(row, "password_hash")?, created_at: crate::row_decode::text(row, "created_at")?, updated_at: crate::row_decode::text(row, "updated_at")?, is_private: row.try_get("is_private")? })
}
}
impl<'r> sqlx::FromRow<'r, AnyRow> for Note {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
let protected: i64 = row.try_get("protected")?;
Ok(Self { id: row.try_get("id")?, _workspace_id: row.try_get("workspace_id")?, slug: crate::row_decode::text(row, "slug")?, title: crate::row_decode::text(row, "title")?, content: crate::row_decode::text(row, "content")?, created_at: crate::row_decode::text(row, "created_at")?, updated_at: crate::row_decode::text(row, "updated_at")?, owner_map: crate::row_decode::text(row, "owner_map")?, protected: protected != 0, created_by: crate::row_decode::optional_text(row, "created_by")? })
}
}
impl<'r> sqlx::FromRow<'r, AnyRow> for Revision {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
Ok(Self { id: row.try_get("id")?, content: crate::row_decode::text(row, "content")?, created_at: crate::row_decode::text(row, "created_at")?, author: crate::row_decode::optional_text(row, "author")?, owner_map: crate::row_decode::text(row, "owner_map")? })
}
}
impl<'r> sqlx::FromRow<'r, AnyRow> for Pad {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> {
Ok(Self { id: row.try_get("id")?, slug: crate::row_decode::text(row, "slug")?, title: crate::row_decode::text(row, "title")?, content: crate::row_decode::text(row, "content")?, password_hash: crate::row_decode::optional_text(row, "password_hash")?, created_at: crate::row_decode::text(row, "created_at")?, updated_at: crate::row_decode::text(row, "updated_at")?, owner_map: crate::row_decode::text(row, "owner_map")?, is_private: row.try_get("is_private")? })
}
}
impl<'r> sqlx::FromRow<'r, AnyRow> for PublishedPageRow {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> { Ok(Self { token: crate::row_decode::text(row,"token")?, pad_id: row.try_get("pad_id")?, note_id: row.try_get("note_id")?, allow_task_updates: row.try_get("allow_task_updates")?, title: crate::row_decode::text(row,"title")?, content: crate::row_decode::text(row,"content")?, updated_at: crate::row_decode::text(row,"updated_at")? }) }
}
impl<'r> sqlx::FromRow<'r, AnyRow> for PostgresPublishedPageRow {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> { Ok(Self { token: crate::row_decode::text(row,"token")?, pad_id: row.try_get("pad_id")?, note_id: row.try_get("note_id")?, allow_task_updates: row.try_get("allow_task_updates")?, title: crate::row_decode::text(row,"title")?, content: crate::row_decode::text(row,"content")?, updated_at: crate::row_decode::text(row,"updated_at")? }) }
}
impl<'r> sqlx::FromRow<'r, AnyRow> for NoteFile {
fn from_row(row: &'r AnyRow) -> Result<Self, sqlx::Error> { let attached:i64=row.try_get("is_attached")?; Ok(Self { id:row.try_get("id")?, filename:crate::row_decode::text(row,"filename")?, url:crate::row_decode::text(row,"url")?, mime_type:crate::row_decode::text(row,"mime_type")?, size_bytes:row.try_get("size_bytes")?, created_at:crate::row_decode::text(row,"created_at")?, is_attached:attached!=0, detached_at:crate::row_decode::optional_text(row,"detached_at")? }) }
}
+1
View File
@@ -1,3 +1,4 @@
mod row_decode;
mod api;
mod app;
mod assets;
+11 -7
View File
@@ -129,9 +129,9 @@ pub const Q001_MYSQL: &str = "SELECT id, slug, CAST(title AS CHAR CHARACTER SET
pub const Q001_POSTGRES: &str = "SELECT id, slug, title, password_hash, created_at, updated_at, CAST(CASE WHEN is_private THEN 1 ELSE 0 END AS BIGINT) AS is_private FROM workspaces WHERE slug = ?";
pub const Q002: &str = "INSERT INTO workspaces (slug, title, password_hash) VALUES (?, ?, ?)";
pub const Q003: &str = "SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, protected, created_by FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC";
pub const Q003_MYSQL: &str = "SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC";
pub const Q003_MYSQL: &str = "SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by FROM notes WHERE workspace_id = ? ORDER BY updated_at DESC, id DESC";
pub const Q004: &str = "SELECT id, workspace_id, slug, title, content, created_at, updated_at, owner_map, protected, created_by FROM notes WHERE workspace_id = ? AND slug = ?";
pub const Q004_MYSQL: &str = "SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by FROM notes WHERE workspace_id = ? AND slug = ?";
pub const Q004_MYSQL: &str = "SELECT id, workspace_id, slug, CAST(title AS CHAR CHARACTER SET utf8mb4) AS title, CAST(content AS CHAR CHARACTER SET utf8mb4) AS content, created_at, updated_at, CAST(owner_map AS CHAR CHARACTER SET utf8mb4) AS owner_map, CAST(CASE WHEN protected THEN 1 ELSE 0 END AS SIGNED) AS protected, CAST(created_by AS CHAR CHARACTER SET utf8mb4) AS created_by FROM notes WHERE workspace_id = ? AND slug = ?";
pub const Q005: &str =
"INSERT INTO notes (workspace_id, slug, title, protected, created_by) VALUES (?, ?, ?, ?, ?)";
pub const Q006: &str =
@@ -159,7 +159,7 @@ pub const Q018: &str = "INSERT INTO published_pages (token, pad_id) VALUES (?, ?
pub const Q019: &str = "SELECT token FROM published_pages WHERE note_id = ?";
pub const Q020: &str = "INSERT INTO published_pages (token, note_id) VALUES (?, ?)";
pub const Q021: &str = "SELECT pp.token, pp.pad_id, pp.note_id, CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS allow_task_updates, 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 = ?";
pub const Q021_MYSQL: &str = "SELECT pp.token, pp.pad_id, pp.note_id, CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS allow_task_updates, CAST(COALESCE(p.title, n.title) AS CHAR CHARACTER SET utf8mb4) AS title, CAST(COALESCE(p.content, n.content) AS CHAR CHARACTER SET utf8mb4) 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 = ?";
pub const Q021_MYSQL: &str = "SELECT pp.token, pp.pad_id, pp.note_id, CAST(CASE WHEN pp.allow_task_updates THEN 1 ELSE 0 END AS SIGNED) AS allow_task_updates, CAST(COALESCE(p.title, n.title) AS CHAR CHARACTER SET utf8mb4) AS title, CAST(COALESCE(p.content, n.content) AS CHAR CHARACTER SET utf8mb4) 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 = ?";
pub const Q022: &str = "SELECT file_token FROM pads WHERE id = ?";
pub const Q023: &str = "UPDATE pads SET file_token = ? WHERE id = ? AND file_token IS NULL";
pub const Q024: &str = "SELECT file_token FROM notes WHERE id = ?";
@@ -176,18 +176,18 @@ pub const Q031: &str = "DELETE FROM notes WHERE id = ?";
pub const Q032: &str =
"INSERT INTO note_files (note_id, filename, url, mime_type, size_bytes) VALUES (?, ?, ?, ?, ?)";
pub const Q033: &str = "SELECT id, filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM note_files WHERE note_id = ? ORDER BY id DESC";
pub const Q033_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM note_files WHERE note_id = ? ORDER BY id DESC";
pub const Q033_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, CAST(CASE WHEN is_attached THEN 1 ELSE 0 END AS SIGNED) AS is_attached, detached_at FROM note_files WHERE note_id = ? ORDER BY id DESC";
pub const Q034: &str = "UPDATE note_files SET is_attached = ?, detached_at = ? WHERE id = ?";
pub const Q035: &str =
"INSERT INTO pad_files (pad_id, filename, url, mime_type, size_bytes) VALUES (?, ?, ?, ?, ?)";
pub const Q036: &str = "SELECT id, filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM pad_files WHERE pad_id = ? ORDER BY id DESC";
pub const Q036_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM pad_files WHERE pad_id = ? ORDER BY id DESC";
pub const Q036_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, CAST(CASE WHEN is_attached THEN 1 ELSE 0 END AS SIGNED) AS is_attached, detached_at FROM pad_files WHERE pad_id = ? ORDER BY id DESC";
pub const Q037: &str = "UPDATE pad_files SET is_attached = ?, detached_at = ? WHERE id = ?";
pub const Q038: &str = "SELECT id, filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM note_files WHERE id = ? AND note_id = ?";
pub const Q038_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM note_files WHERE id = ? AND note_id = ?";
pub const Q038_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, CAST(CASE WHEN is_attached THEN 1 ELSE 0 END AS SIGNED) AS is_attached, detached_at FROM note_files WHERE id = ? AND note_id = ?";
pub const Q039: &str = "DELETE FROM note_files WHERE id = ? AND note_id = ?";
pub const Q046: &str = "SELECT id, filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM pad_files WHERE id = ? AND pad_id = ?";
pub const Q046_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, is_attached, detached_at FROM pad_files WHERE id = ? AND pad_id = ?";
pub const Q046_MYSQL: &str = "SELECT id, CAST(filename AS CHAR CHARACTER SET utf8mb4) AS filename, url, mime_type, size_bytes, created_at, CAST(CASE WHEN is_attached THEN 1 ELSE 0 END AS SIGNED) AS is_attached, detached_at FROM pad_files WHERE id = ? AND pad_id = ?";
pub const Q047: &str = "DELETE FROM pad_files WHERE id = ? AND pad_id = ?";
static POSTGRES_QUERIES: OnceLock<Mutex<HashMap<&'static str, &'static str>>> = OnceLock::new();
@@ -220,6 +220,8 @@ pub fn get(kind: DatabaseKind, query: &'static str) -> &'static str {
(DatabaseKind::MySql, Q036) => Q036_MYSQL,
(DatabaseKind::MySql, Q038) => Q038_MYSQL,
(DatabaseKind::MySql, Q046) => Q046_MYSQL,
(DatabaseKind::MySql, Q044) => Q044_MYSQL,
(DatabaseKind::MySql, Q045) => Q045_MYSQL,
_ => query,
};
@@ -258,6 +260,8 @@ pub const Q044: &str =
"SELECT CASE WHEN allow_task_updates THEN 1 ELSE 0 END FROM published_pages WHERE pad_id = ?";
pub const Q045: &str =
"SELECT CASE WHEN allow_task_updates THEN 1 ELSE 0 END FROM published_pages WHERE note_id = ?";
pub const Q044_MYSQL: &str = "SELECT CAST(CASE WHEN allow_task_updates THEN 1 ELSE 0 END AS SIGNED) FROM published_pages WHERE pad_id = ?";
pub const Q045_MYSQL: &str = "SELECT CAST(CASE WHEN allow_task_updates THEN 1 ELSE 0 END AS SIGNED) FROM published_pages WHERE note_id = ?";
pub const Q021_POSTGRES: &str = "SELECT pp.token, pp.pad_id, pp.note_id, pp.allow_task_updates, 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 = $1";
pub const Q044_POSTGRES: &str = "SELECT allow_task_updates FROM published_pages WHERE pad_id = $1";
+30
View File
@@ -0,0 +1,30 @@
use sqlx::{any::AnyRow, ColumnIndex, Error, Row};
pub fn text<I>(row: &AnyRow, index: I) -> Result<String, Error>
where
I: ColumnIndex<AnyRow> + Copy,
{
match row.try_get::<String, _>(index) {
Ok(value) => Ok(value),
Err(string_error) => match row.try_get::<Vec<u8>, _>(index) {
Ok(value) => String::from_utf8(value).map_err(|error| Error::Decode(Box::new(error))),
Err(_) => Err(string_error),
},
}
}
pub fn optional_text<I>(row: &AnyRow, index: I) -> Result<Option<String>, Error>
where
I: ColumnIndex<AnyRow> + Copy,
{
match row.try_get::<Option<String>, _>(index) {
Ok(value) => Ok(value),
Err(string_error) => match row.try_get::<Option<Vec<u8>>, _>(index) {
Ok(Some(value)) => String::from_utf8(value)
.map(Some)
.map_err(|error| Error::Decode(Box::new(error))),
Ok(None) => Ok(None),
Err(_) => Err(string_error),
},
}
}