update rustpad
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+30
-3
@@ -20,7 +20,7 @@ use lettre::{
|
|||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use sqlx::FromRow;
|
use sqlx::{any::AnyRow, FromRow, Row};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -32,7 +32,7 @@ const MIN_PASSWORD: usize = 8;
|
|||||||
const MAX_PASSWORD: usize = 128;
|
const MAX_PASSWORD: usize = 128;
|
||||||
const MAX_NICKNAME: usize = 40;
|
const MAX_NICKNAME: usize = 40;
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub nickname: String,
|
pub nickname: String,
|
||||||
@@ -103,7 +103,7 @@ pub struct ResourceActionRequest {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
}
|
}
|
||||||
#[derive(Serialize, FromRow)]
|
#[derive(Serialize)]
|
||||||
pub struct ResourceItem {
|
pub struct ResourceItem {
|
||||||
slug: String,
|
slug: String,
|
||||||
title: String,
|
title: String,
|
||||||
@@ -117,6 +117,33 @@ pub struct ResourceItem {
|
|||||||
shared_by: String,
|
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)]
|
#[derive(Deserialize)]
|
||||||
pub struct PrivacyRequest {
|
pub struct PrivacyRequest {
|
||||||
kind: String,
|
kind: String,
|
||||||
|
|||||||
@@ -7,32 +7,7 @@ use chrono::{DateTime, NaiveDateTime, Utc};
|
|||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::FromRow;
|
use sqlx::FromRow;
|
||||||
use sqlx::{Any, Row, Transaction, any::AnyRow};
|
use sqlx::{any::AnyRow, Any, Row, Transaction};
|
||||||
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn inserted_id(
|
async fn inserted_id(
|
||||||
kind: DatabaseKind,
|
kind: DatabaseKind,
|
||||||
@@ -51,7 +26,7 @@ async fn inserted_id(
|
|||||||
sqlx::query_scalar(query).fetch_one(&mut **tx).await
|
sqlx::query_scalar(query).fetch_one(&mut **tx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
@@ -62,19 +37,7 @@ pub struct Workspace {
|
|||||||
pub is_private: i64,
|
pub is_private: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_from_any_row(row: AnyRow) -> Result<Workspace, sqlx::Error> {
|
#[derive(Debug, Clone, Serialize)]
|
||||||
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)]
|
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
@@ -122,22 +85,7 @@ impl From<SqliteNote> for Note {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_from_any_row(row: AnyRow) -> Result<Note, sqlx::Error> {
|
#[derive(Debug, Serialize)]
|
||||||
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)]
|
|
||||||
pub struct Revision {
|
pub struct Revision {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
@@ -146,25 +94,7 @@ pub struct Revision {
|
|||||||
pub owner_map: String,
|
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> {
|
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))
|
sqlx::query_as::<_, Workspace>(queries::get(pool.kind(), queries::Q001))
|
||||||
.bind(slug)
|
.bind(slug)
|
||||||
.fetch_optional(pool.pool())
|
.fetch_optional(pool.pool())
|
||||||
@@ -187,14 +117,6 @@ pub async fn create_workspace(
|
|||||||
.execute(pool.pool())
|
.execute(pool.pool())
|
||||||
.await?;
|
.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))
|
sqlx::query_as::<_, Workspace>(queries::get(pool.kind(), queries::Q001))
|
||||||
.bind(slug)
|
.bind(slug)
|
||||||
.fetch_one(pool.pool())
|
.fetch_one(pool.pool())
|
||||||
@@ -229,15 +151,6 @@ pub async fn list_notes(pool: &Database, workspace_id: i64) -> Result<Vec<Note>,
|
|||||||
.map(Note::from)
|
.map(Note::from)
|
||||||
.collect());
|
.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))
|
sqlx::query_as::<_, Note>(queries::get(pool.kind(), queries::Q003))
|
||||||
.bind(workspace_id)
|
.bind(workspace_id)
|
||||||
.fetch_all(pool.pool())
|
.fetch_all(pool.pool())
|
||||||
@@ -257,15 +170,6 @@ pub async fn find_note(
|
|||||||
.await?
|
.await?
|
||||||
.map(Note::from));
|
.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))
|
sqlx::query_as::<_, Note>(queries::get(pool.kind(), queries::Q004))
|
||||||
.bind(workspace_id)
|
.bind(workspace_id)
|
||||||
.bind(slug)
|
.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> {
|
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))
|
sqlx::query_as::<_, Revision>(queries::get(pool.kind(), queries::Q010))
|
||||||
.bind(note_id)
|
.bind(note_id)
|
||||||
.fetch_all(pool.pool())
|
.fetch_all(pool.pool())
|
||||||
@@ -406,7 +301,7 @@ pub fn normalize_timestamp(value: &str) -> String {
|
|||||||
value.to_string()
|
value.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Pad {
|
pub struct Pad {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
@@ -419,29 +314,7 @@ pub struct Pad {
|
|||||||
pub is_private: i64,
|
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> {
|
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))
|
sqlx::query_as::<_, Pad>(queries::get(pool.kind(), queries::Q011))
|
||||||
.bind(slug)
|
.bind(slug)
|
||||||
.fetch_optional(pool.pool())
|
.fetch_optional(pool.pool())
|
||||||
@@ -464,14 +337,6 @@ pub async fn create_pad(
|
|||||||
.execute(pool.pool())
|
.execute(pool.pool())
|
||||||
.await?;
|
.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))
|
sqlx::query_as::<_, Pad>(queries::get(pool.kind(), queries::Q011))
|
||||||
.bind(slug)
|
.bind(slug)
|
||||||
.fetch_one(pool.pool())
|
.fetch_one(pool.pool())
|
||||||
@@ -530,15 +395,6 @@ pub async fn list_pad_revisions(
|
|||||||
pool: &Database,
|
pool: &Database,
|
||||||
pad_id: i64,
|
pad_id: i64,
|
||||||
) -> Result<Vec<Revision>, sqlx::Error> {
|
) -> 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))
|
sqlx::query_as::<_, Revision>(queries::get(pool.kind(), queries::Q016))
|
||||||
.bind(pad_id)
|
.bind(pad_id)
|
||||||
.fetch_all(pool.pool())
|
.fetch_all(pool.pool())
|
||||||
@@ -556,7 +412,7 @@ pub struct PublishedPage {
|
|||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone)]
|
||||||
struct PublishedPageRow {
|
struct PublishedPageRow {
|
||||||
token: String,
|
token: String,
|
||||||
pad_id: Option<i64>,
|
pad_id: Option<i64>,
|
||||||
@@ -567,7 +423,7 @@ struct PublishedPageRow {
|
|||||||
updated_at: String,
|
updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone)]
|
||||||
struct PostgresPublishedPageRow {
|
struct PostgresPublishedPageRow {
|
||||||
token: String,
|
token: String,
|
||||||
pad_id: Option<i64>,
|
pad_id: Option<i64>,
|
||||||
@@ -863,7 +719,7 @@ pub async fn find_file_owner(
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, FromRow)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct NoteFile {
|
pub struct NoteFile {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
@@ -1079,3 +935,35 @@ pub async fn delete_pad_file(
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
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,3 +1,4 @@
|
|||||||
|
mod row_decode;
|
||||||
mod api;
|
mod api;
|
||||||
mod app;
|
mod app;
|
||||||
mod assets;
|
mod assets;
|
||||||
|
|||||||
+11
-7
@@ -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 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 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: &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: &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 =
|
pub const Q005: &str =
|
||||||
"INSERT INTO notes (workspace_id, slug, title, protected, created_by) VALUES (?, ?, ?, ?, ?)";
|
"INSERT INTO notes (workspace_id, slug, title, protected, created_by) VALUES (?, ?, ?, ?, ?)";
|
||||||
pub const Q006: &str =
|
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 Q019: &str = "SELECT token FROM published_pages WHERE note_id = ?";
|
||||||
pub const Q020: &str = "INSERT INTO published_pages (token, note_id) VALUES (?, ?)";
|
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: &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 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 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 = ?";
|
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 =
|
pub const Q032: &str =
|
||||||
"INSERT INTO note_files (note_id, filename, url, mime_type, size_bytes) VALUES (?, ?, ?, ?, ?)";
|
"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: &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 Q034: &str = "UPDATE note_files SET is_attached = ?, detached_at = ? WHERE id = ?";
|
||||||
pub const Q035: &str =
|
pub const Q035: &str =
|
||||||
"INSERT INTO pad_files (pad_id, filename, url, mime_type, size_bytes) VALUES (?, ?, ?, ?, ?)";
|
"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: &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 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: &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 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: &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 = ?";
|
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();
|
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, Q036) => Q036_MYSQL,
|
||||||
(DatabaseKind::MySql, Q038) => Q038_MYSQL,
|
(DatabaseKind::MySql, Q038) => Q038_MYSQL,
|
||||||
(DatabaseKind::MySql, Q046) => Q046_MYSQL,
|
(DatabaseKind::MySql, Q046) => Q046_MYSQL,
|
||||||
|
(DatabaseKind::MySql, Q044) => Q044_MYSQL,
|
||||||
|
(DatabaseKind::MySql, Q045) => Q045_MYSQL,
|
||||||
_ => query,
|
_ => 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 = ?";
|
"SELECT CASE WHEN allow_task_updates THEN 1 ELSE 0 END FROM published_pages WHERE pad_id = ?";
|
||||||
pub const Q045: &str =
|
pub const Q045: &str =
|
||||||
"SELECT CASE WHEN allow_task_updates THEN 1 ELSE 0 END FROM published_pages WHERE note_id = ?";
|
"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 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";
|
pub const Q044_POSTGRES: &str = "SELECT allow_task_updates FROM published_pages WHERE pad_id = $1";
|
||||||
|
|||||||
@@ -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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user