1
0
This commit is contained in:
2025-04-03 03:27:45 +03:00
parent db61f2210b
commit bc9d31c385
12 changed files with 246 additions and 107 deletions

View File

@@ -4,4 +4,4 @@ port = 1234
[database]
max_connections = 5
url = "sqlite://nir.db?mode=rwc"
url = "postgres://postgres:123456789@localhost:5432/postgres"

View File

@@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS user (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`avatar` VARCHAR,
`username` VARCHAR UNIQUE NOT NULL,
`password_hash` VARCHAR NOT NULL,
`last_seen` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS "user" (
"id" SERIAL PRIMARY KEY,
"avatar" VARCHAR,
"username" VARCHAR UNIQUE NOT NULL,
"password_hash" VARCHAR NOT NULL,
"last_seen" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@@ -1,28 +1,28 @@
CREATE TABLE IF NOT EXISTS `channel` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR,
`last_message_id` INTEGER,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS "channel" (
"id" SERIAL PRIMARY KEY,
"name" VARCHAR,
"last_message_id" BIGINT,
"created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `channel_user` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`channel_id` INTEGER NOT NULL,
`user_id` INTEGER NOT NULL,
`admin` BOOLEAN NOT NULL DEFAULT 0,
FOREIGN KEY(`channel_id`) REFERENCES `channel`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE
CREATE TABLE IF NOT EXISTS "channel_user" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"admin" BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY("channel_id") REFERENCES "channel"("id") ON DELETE CASCADE,
FOREIGN KEY("user_id") REFERENCES "user"("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `message` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`channel_id` INTEGER NOT NULL,
`author_id` INTEGER NOT NULL,
`content` TEXT NOT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`system` BOOLEAN NOT NULL DEFAULT 0,
FOREIGN KEY(`channel_id`) REFERENCES `channel`(`id`) ON DELETE CASCADE,
FOREIGN KEY(`author_id`) REFERENCES `user`(`id`) ON DELETE
CREATE TABLE IF NOT EXISTS "message" (
"id" BIGSERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL,
"author_id" INTEGER NOT NULL,
"content" TEXT NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"system" BOOLEAN NOT NULL DEFAULT FALSE,
FOREIGN KEY("channel_id") REFERENCES "channel"("id") ON DELETE CASCADE,
FOREIGN KEY("author_id") REFERENCES "user"("id") ON DELETE
SET
NULL
);

View File

@@ -1,6 +1,6 @@
CREATE TABLE IF NOT EXISTS tokens (
`token` TEXT NOT NULL PRIMARY KEY,
`user_id` INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`expires_at` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS "tokens" (
"token" TEXT NOT NULL PRIMARY KEY,
"user_id" INTEGER NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
"created_at" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMPTZ NOT NULL
);

View File

@@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS user_follow (
`user_id` INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
`follow_id` INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, follow_id),
CHECK (user_id <> follow_id)
);
CREATE TABLE IF NOT EXISTS "user_follow" (
"user_id" INTEGER NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
"follow_id" INTEGER NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("user_id", "follow_id"),
CHECK ("user_id" <> "follow_id")
);

View File

@@ -1,15 +1,15 @@
CREATE TABLE IF NOT EXISTS secret (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` TEXT NOT NULL,
`content` TEXT NOT NULL,
`user_id` INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
`timeout_seconds` INTEGER NOT NULL,
`expired` BOOLEAN NOT NULL DEFAULT 0,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS "secret" (
"id" SERIAL PRIMARY KEY,
"name" TEXT NOT NULL,
"content" TEXT NOT NULL,
"user_id" INTEGER NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
"timeout_seconds" INTEGER NOT NULL,
"expired" BOOLEAN NOT NULL DEFAULT FALSE,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS secret_recipient (
`secret_id` INTEGER NOT NULL REFERENCES secret(id) ON DELETE CASCADE,
`user_id` INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
PRIMARY KEY (secret_id, user_id)
);
CREATE TABLE IF NOT EXISTS "secret_recipient" (
"secret_id" INTEGER NOT NULL REFERENCES "secret"("id") ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
PRIMARY KEY ("secret_id", "user_id")
);

View File

@@ -1,8 +1,8 @@
CREATE TABLE IF NOT EXISTS notification (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`user_id` INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE,
`title` TEXT NOT NULL,
`body` TEXT NOT NULL,
`seen` BOOLEAN NOT NULL DEFAULT 0,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS "notification" (
"id" BIGSERIAL PRIMARY KEY,
"user_id" INTEGER NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"seen" BOOLEAN NOT NULL DEFAULT FALSE,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@@ -0,0 +1,32 @@
CREATE OR REPLACE FUNCTION create_message_and_update_channel(
p_channel_id INTEGER,
p_author_id INTEGER,
p_content TEXT
)
RETURNS BIGINT
LANGUAGE plpgsql
AS $$
DECLARE
v_message_id BIGINT;
BEGIN
INSERT INTO "message"("channel_id", "author_id", "content")
VALUES (p_channel_id, p_author_id, p_content)
RETURNING "id" INTO v_message_id;
UPDATE "channel"
SET "last_message_id" = v_message_id
WHERE "id" = p_channel_id;
RETURN v_message_id;
END;
$$;
CREATE OR REPLACE PROCEDURE update_user_last_seen(p_user_id INTEGER)
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE "user"
SET "last_seen" = CURRENT_TIMESTAMP
WHERE "id" = p_user_id;
END;
$$;

View File

@@ -1,5 +1,6 @@
use std::ops::Deref;
use derive_more::{Display, Error, From};
use sqlx::migrate::Migrator;
use sqlx::migrate::{Migrate, Migrator};
use crate::{
config,
@@ -10,26 +11,38 @@ static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
#[derive(Clone)]
pub struct Database {
pool: sqlx::AnyPool,
pool: sqlx::PgPool,
}
impl Database {
pub async fn init() -> Result<Self> {
let config = config::config();
let pool = sqlx::any::AnyPoolOptions::new()
.max_connections(config.database.max_connections)
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(config.database.max_connections);
let pool = pool
.connect(&config.database.url)
.await
.inspect_err(|e| tracing::error!("Could not connect to database: {e}"))?;
// Test connection
{
let connection = pool.acquire().await?;
let version = connection.deref().server_version_num();
tracing::info!("Connected to postgres version {}", match version {
Some(version) => version.to_string(),
None => "unknown".to_string(),
});
}
MIGRATOR.run(&pool).await?;
Ok(Self { pool })
}
pub async fn get_user_by_id(&self, user_id: entity::ShortId) -> Result<entity::User> {
let user = sqlx::query_as("SELECT * FROM user WHERE id = $1")
let user = sqlx::query_as(r#"SELECT * FROM "user" WHERE "id" = $1"#)
.bind(user_id)
.fetch_optional(&self.pool)
.await?
@@ -39,7 +52,7 @@ impl Database {
}
pub async fn get_user_by_username(&self, username: &str) -> Result<entity::User> {
let user = sqlx::query_as("SELECT * FROM user WHERE username = $1")
let user = sqlx::query_as(r#"SELECT * FROM "user" WHERE "username" = $1"#)
.bind(username)
.fetch_optional(&self.pool)
.await?
@@ -55,7 +68,7 @@ impl Database {
}
let id = sqlx::query_scalar(
"INSERT INTO user(username, password_hash) VALUES ($1, $2) RETURNING id",
r#"INSERT INTO "user"("username", "password_hash") VALUES ($1, $2) RETURNING "id""#,
)
.bind(username)
.bind(password_hash)
@@ -68,7 +81,7 @@ impl Database {
}
pub async fn update_user_last_seen(&self, user_id: entity::ShortId) -> Result<()> {
sqlx::query("UPDATE user SET last_seen = CURRENT_TIMESTAMP WHERE id = $1")
sqlx::query(r#"CALL update_user_last_seen($1)"#)
.bind(user_id)
.execute(&self.pool)
.await?;
@@ -77,7 +90,7 @@ impl Database {
}
pub async fn activate_all_secrets(&self, user_id: entity::ShortId) -> Result<()> {
sqlx::query("UPDATE secret SET expired = false WHERE user_id = $1")
sqlx::query(r#"UPDATE "secret" SET "expired" = false WHERE "user_id" = $1"#)
.bind(user_id)
.execute(&self.pool)
.await?;
@@ -91,7 +104,7 @@ impl Database {
token: &str,
expires_at: chrono::DateTime<chrono::Utc>,
) -> Result<entity::Token> {
sqlx::query("INSERT INTO tokens(user_id, token, expires_at) VALUES ($1, $2, $3)")
sqlx::query(r#"INSERT INTO "tokens"("user_id", "token", "expires_at") VALUES ($1, $2, $3)"#)
.bind(id)
.bind(token)
.bind(expires_at)
@@ -102,7 +115,7 @@ impl Database {
}
pub async fn get_token(&self, token: &str) -> Result<entity::Token> {
let token = sqlx::query_as("SELECT * FROM tokens WHERE token = $1")
let token = sqlx::query_as(r#"SELECT * FROM "tokens" WHERE "token" = $1"#)
.bind(token)
.fetch_optional(&self.pool)
.await?
@@ -112,7 +125,7 @@ impl Database {
}
pub async fn get_channel_by_id(&self, channel_id: entity::ShortId) -> Result<entity::Channel> {
let channel = sqlx::query_as("SELECT * FROM channel WHERE id = $1")
let channel = sqlx::query_as(r#"SELECT * FROM "channel" WHERE "id" = $1"#)
.bind(channel_id)
.fetch_optional(&self.pool)
.await?
@@ -125,7 +138,7 @@ impl Database {
&self,
user_id: entity::ShortId,
) -> Result<Vec<entity::Channel>> {
let channels = sqlx::query_as("SELECT channel.* FROM channel INNER JOIN channel_user ON channel.id = channel_user.channel_id WHERE user_id = $1")
let channels = sqlx::query_as(r#"SELECT "channel".* FROM "channel" INNER JOIN "channel_user" ON "channel"."id" = "channel_user"."channel_id" WHERE "user_id" = $1"#)
.bind(user_id)
.fetch_all(&self.pool)
.await?;
@@ -138,7 +151,7 @@ impl Database {
channel_id: entity::ShortId,
message_id: entity::LongId,
) -> Result<entity::Channel> {
sqlx::query("UPDATE channel SET last_message_id = $1 WHERE id = $2")
sqlx::query(r#"UPDATE "channel" SET "last_message_id" = $1 WHERE "id" = $2"#)
.bind(message_id)
.bind(channel_id)
.execute(&self.pool)
@@ -153,7 +166,7 @@ impl Database {
channel_id: entity::ShortId,
admin: bool,
) -> Result<()> {
sqlx::query("INSERT INTO channel_user(user_id, channel_id, admin) VALUES ($1, $2, $3)")
sqlx::query(r#"INSERT INTO "channel_user"("user_id", "channel_id", "admin") VALUES ($1, $2, $3)"#)
.bind(user_id)
.bind(channel_id)
.bind(admin)
@@ -168,7 +181,7 @@ impl Database {
user_id: entity::ShortId,
channel_id: entity::ShortId,
) -> Result<()> {
sqlx::query("DELETE FROM channel_user WHERE user_id = $1 AND channel_id = $2")
sqlx::query(r#"DELETE FROM "channel_user" WHERE "user_id" = $1 AND "channel_id" = $2"#)
.bind(user_id)
.bind(channel_id)
.execute(&self.pool)
@@ -182,7 +195,7 @@ impl Database {
user_id: entity::ShortId,
name: &str,
) -> Result<entity::Channel> {
let id = sqlx::query_scalar("INSERT INTO channel(name) VALUES ($1) RETURNING id")
let id = sqlx::query_scalar(r#"INSERT INTO "channel"("name") VALUES ($1) RETURNING "id""#)
.bind(name)
.fetch_one(&self.pool)
.await?;
@@ -192,7 +205,7 @@ impl Database {
}
pub async fn delete_channel(&self, channel_id: entity::ShortId) -> Result<()> {
sqlx::query("DELETE FROM channel WHERE id = $1")
sqlx::query(r#"DELETE FROM "channel" WHERE "id" = $1"#)
.bind(channel_id)
.execute(&self.pool)
.await?;
@@ -205,7 +218,7 @@ impl Database {
channel_id: entity::ShortId,
message_id: entity::LongId,
) -> Result<()> {
sqlx::query("UPDATE channel SET last_message_id = $1 WHERE id = $2")
sqlx::query(r#"UPDATE "channel" SET "last_message_id" = $1 WHERE "id" = $2"#)
.bind(message_id)
.bind(channel_id)
.execute(&self.pool)
@@ -219,7 +232,7 @@ impl Database {
channel_id: entity::ShortId,
) -> Result<Vec<(entity::User, bool)>> {
let user_ids =
sqlx::query_as("SELECT user_id, admin FROM channel_user WHERE channel_id = $1")
sqlx::query_as(r#"SELECT "user_id", "admin" FROM "channel_user" WHERE "channel_id" = $1"#)
.bind(channel_id)
.fetch_all(&self.pool)
.await?;
@@ -235,7 +248,7 @@ impl Database {
}
pub async fn get_message_by_id(&self, message_id: entity::LongId) -> Result<entity::Message> {
let message = sqlx::query_as("SELECT * FROM message WHERE id = $1")
let message = sqlx::query_as(r#"SELECT * FROM "message" WHERE "id" = $1"#)
.bind(message_id)
.fetch_optional(&self.pool)
.await?
@@ -250,8 +263,19 @@ impl Database {
user_id: entity::ShortId,
content: &str,
) -> Result<entity::Message> {
// let id = sqlx::query_scalar(
// r#"INSERT INTO "message"("channel_id", "author_id", "content") VALUES ($1, $2, $3) RETURNING "id""#,
// )
// .bind(channel_id)
// .bind(user_id)
// .bind(content)
// .fetch_one(&self.pool)
// .await?;
// self.set_channel_last_message_id(channel_id, id).await?;
let id = sqlx::query_scalar(
"INSERT INTO message(channel_id, author_id, content) VALUES ($1, $2, $3) RETURNING id",
"SELECT create_message_and_update_channel($1, $2, $3)"
)
.bind(channel_id)
.bind(user_id)
@@ -259,7 +283,6 @@ impl Database {
.fetch_one(&self.pool)
.await?;
self.set_channel_last_message_id(channel_id, id).await?;
self.get_message_by_id(id).await
}
@@ -271,7 +294,7 @@ impl Database {
) -> Result<Vec<entity::Message>> {
let messages = match before_id {
Some(before_id) => sqlx::query_as::<_, entity::Message>(
"SELECT * FROM message WHERE channel_id = $1 AND id < $2 ORDER BY id DESC LIMIT $3",
r#"SELECT * FROM "message" WHERE "channel_id" = $1 AND "id" < $2 ORDER BY "id" DESC LIMIT $3"#,
)
.bind(channel_id)
.bind(before_id)
@@ -280,7 +303,7 @@ impl Database {
.await?,
None => {
sqlx::query_as::<_, entity::Message>(
"SELECT * FROM message WHERE channel_id = $1 ORDER BY id DESC LIMIT $2",
r#"SELECT * FROM "message" WHERE "channel_id" = $1 ORDER BY "id" DESC LIMIT $2"#,
)
.bind(channel_id)
.bind(limit)
@@ -293,7 +316,7 @@ impl Database {
}
pub async fn get_followed_users(&self, user_id: entity::ShortId) -> Result<Vec<entity::User>> {
let users = sqlx::query_as("SELECT user.* FROM user_follow JOIN user ON user.id = user_follow.follow_id WHERE user_id = $1")
let users = sqlx::query_as(r#"SELECT "user".* FROM "user_follow" JOIN "user" ON "user"."id" = "user_follow"."follow_id" WHERE "user_id" = $1"#)
.bind(user_id)
.fetch_all(&self.pool)
.await?;
@@ -306,7 +329,7 @@ impl Database {
user_id: entity::ShortId,
follow_id: entity::ShortId,
) -> Result<()> {
sqlx::query("INSERT INTO user_follow(user_id, follow_id) VALUES ($1, $2)")
sqlx::query(r#"INSERT INTO "user_follow"("user_id", "follow_id") VALUES ($1, $2)"#)
.bind(user_id)
.bind(follow_id)
.execute(&self.pool)
@@ -320,7 +343,7 @@ impl Database {
user_id: entity::ShortId,
follow_id: entity::ShortId,
) -> Result<()> {
sqlx::query("DELETE FROM user_follow WHERE user_id = $1 AND follow_id = $2")
sqlx::query(r#"DELETE FROM "user_follow" WHERE "user_id" = $1 AND "follow_id" = $2"#)
.bind(user_id)
.bind(follow_id)
.execute(&self.pool)
@@ -336,7 +359,7 @@ impl Database {
offset: i64,
) -> Result<Vec<entity::User>> {
let users = sqlx::query_as(
"SELECT * FROM user WHERE username LIKE $1 ORDER BY username LIMIT $2 OFFSET $3",
r#"SELECT * FROM "user" WHERE "username" LIKE $1 ORDER BY "username" LIMIT $2 OFFSET $3"#,
)
.bind(format!("{}%", username))
.bind(limit)
@@ -353,7 +376,7 @@ impl Database {
channel_id: entity::ShortId,
) -> Result<entity::ChannelPermisions> {
let permissions =
sqlx::query_as("SELECT * FROM channel_user WHERE user_id = $1 AND channel_id = $2")
sqlx::query_as(r#"SELECT * FROM "channel_user" WHERE "user_id" = $1 AND "channel_id" = $2"#)
.bind(user_id)
.bind(channel_id)
.fetch_one(&self.pool)
@@ -363,7 +386,7 @@ impl Database {
}
pub async fn get_secret_by_id(&self, secret_id: entity::ShortId) -> Result<entity::Secret> {
let secret = sqlx::query_as("SELECT * FROM secret WHERE id = $1")
let secret = sqlx::query_as(r#"SELECT * FROM "secret" WHERE "id" = $1"#)
.bind(secret_id)
.fetch_optional(&self.pool)
.await?
@@ -376,7 +399,7 @@ impl Database {
&self,
user_id: entity::ShortId,
) -> Result<Vec<entity::Secret>> {
let secrets = sqlx::query_as("SELECT * FROM secret WHERE user_id = $1")
let secrets = sqlx::query_as(r#"SELECT * FROM "secret" WHERE "user_id" = $1"#)
.bind(user_id)
.fetch_all(&self.pool)
.await?;
@@ -385,7 +408,7 @@ impl Database {
}
pub async fn get_active_all_secrets(&self) -> Result<Vec<entity::Secret>> {
let secrets = sqlx::query_as("SELECT * FROM secret WHERE expired = false")
let secrets = sqlx::query_as(r#"SELECT * FROM "secret" WHERE "expired" = false"#)
.fetch_all(&self.pool)
.await?;
@@ -400,7 +423,7 @@ impl Database {
timeout_seconds: i32,
) -> Result<entity::Secret> {
sqlx::query(
"UPDATE secret SET name = $1, content = $2, timeout_seconds = $3 WHERE id = $4",
r#"UPDATE "secret" SET "name" = $1, "content" = $2, "timeout_seconds" = $3 WHERE "id" = $4"#,
)
.bind(name)
.bind(content)
@@ -420,7 +443,7 @@ impl Database {
timeout_seconds: i32,
) -> Result<entity::Secret> {
let id = sqlx::query_scalar(
"INSERT INTO secret(user_id, name, content, timeout_seconds) VALUES ($1, $2, $3, $4) RETURNING id",
r#"INSERT INTO "secret"("user_id", "name", "content", "timeout_seconds") VALUES ($1, $2, $3, $4) RETURNING "id""#,
)
.bind(user_id)
.bind(name)
@@ -437,7 +460,7 @@ impl Database {
secret_id: entity::ShortId,
user_id: entity::ShortId,
) -> Result<()> {
sqlx::query("INSERT INTO secret_recipient(secret_id, user_id) VALUES ($1, $2)")
sqlx::query(r#"INSERT INTO "secret_recipient"("secret_id", "user_id") VALUES ($1, $2)"#)
.bind(secret_id)
.bind(user_id)
.execute(&self.pool)
@@ -447,7 +470,7 @@ impl Database {
}
pub async fn delete_secret(&self, secret_id: entity::ShortId) -> Result<()> {
sqlx::query("DELETE FROM secret WHERE id = $1")
sqlx::query(r#"DELETE FROM "secret" WHERE "id" = $1"#)
.bind(secret_id)
.execute(&self.pool)
.await?;
@@ -456,7 +479,7 @@ impl Database {
}
pub async fn expire_secret(&self, secret_id: entity::ShortId) -> Result<()> {
sqlx::query("UPDATE secret SET expired = true WHERE id = $1")
sqlx::query(r#"UPDATE "secret" SET "expired" = true WHERE "id" = $1"#)
.bind(secret_id)
.execute(&self.pool)
.await?;
@@ -469,7 +492,7 @@ impl Database {
secret_id: entity::ShortId,
user_id: entity::ShortId,
) -> Result<()> {
sqlx::query("DELETE FROM secret_recipient WHERE secret_id = $1 AND user_id = $2")
sqlx::query(r#"DELETE FROM "secret_recipient" WHERE "secret_id" = $1 AND "user_id" = $2"#)
.bind(secret_id)
.bind(user_id)
.execute(&self.pool)
@@ -482,7 +505,7 @@ impl Database {
&self,
secret_id: entity::ShortId,
) -> Result<Vec<entity::User>> {
let users = sqlx::query_as("SELECT user.* FROM user INNER JOIN secret_recipient ON user.id = secret_recipient.user_id WHERE secret_id = $1")
let users = sqlx::query_as(r#"SELECT "user".* FROM "user" INNER JOIN "secret_recipient" ON "user"."id" = "secret_recipient"."user_id" WHERE "secret_id" = $1"#)
.bind(secret_id)
.fetch_all(&self.pool)
.await?;
@@ -494,7 +517,7 @@ impl Database {
&self,
user_id: entity::ShortId,
) -> Result<Vec<entity::Notification>> {
let notifications = sqlx::query_as("SELECT * FROM notification WHERE user_id = $1")
let notifications = sqlx::query_as(r#"SELECT * FROM "notification" WHERE "user_id" = $1"#)
.bind(user_id)
.fetch_all(&self.pool)
.await?;
@@ -506,7 +529,7 @@ impl Database {
&self,
notification_id: entity::LongId,
) -> Result<entity::Notification> {
let notification = sqlx::query_as("SELECT * FROM notification WHERE id = $1")
let notification = sqlx::query_as(r#"SELECT * FROM "notification" WHERE "id" = $1"#)
.bind(notification_id)
.fetch_optional(&self.pool)
.await?
@@ -522,7 +545,7 @@ impl Database {
body: &str,
) -> Result<entity::Notification> {
let id = sqlx::query_scalar(
"INSERT INTO notification(user_id, title, body) VALUES ($1, $2, $3) RETURNING id",
r#"INSERT INTO "notification"("user_id", "title", "body") VALUES ($1, $2, $3) RETURNING "id""#,
)
.bind(user_id)
.bind(title)
@@ -537,13 +560,29 @@ impl Database {
&self,
notification_id: entity::LongId,
) -> Result<entity::Notification> {
sqlx::query("UPDATE notification SET seen = true WHERE id = $1")
sqlx::query(r#"UPDATE "notification" SET "seen" = true WHERE "id" = $1"#)
.bind(notification_id)
.execute(&self.pool)
.await?;
self.get_notification_by_id(notification_id).await
}
pub async fn get_all_users(&self) -> Result<Vec<entity::User>> {
let users = sqlx::query_as(r#"SELECT * FROM "user""#)
.fetch_all(&self.pool)
.await?;
Ok(users)
}
pub async fn any_fetch_all_query(&self, query: &str) -> Result<Vec<sqlx::postgres::PgRow>> {
let result = sqlx::query(query)
.fetch_all(&self.pool)
.await?;
Ok(result)
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -11,6 +11,7 @@ pub mod ws;
pub use error::{Error, Result};
use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin};
use crate::web::routes::lab;
pub async fn run(state: state::AppState) -> anyhow::Result<()> {
let config = config::config();
@@ -44,6 +45,9 @@ fn router(state: state::AppState) -> axum::Router {
.route("/user/register", post(user::register))
// protected
.nest("/", protected_router())
// lab
.route("/lab/users", get(lab::get_all_users))
.route("/lab/any-query", post(lab::any_fetch_all_query))
.layer(tower_http::trace::TraceLayer::new_for_http())
.route_layer(axum::middleware::from_fn_with_state(
state.clone(),

63
src/web/routes/lab.rs Normal file
View File

@@ -0,0 +1,63 @@
use std::{collections::HashMap, ops::Deref};
use axum::{extract::{Path, State}, response::IntoResponse, Json};
use sqlx::{Column, Row, TypeInfo, ValueRef};
use sqlx::postgres::PgTypeInfo;
use crate::{
entity::LongId,
state::AppState,
web::{self},
};
pub async fn get_all_users(
State(state): State<AppState>,
) -> web::Result<impl IntoResponse> {
let users = state.database.get_all_users().await?;
Ok(axum::response::Json(users))
}
#[derive(serde::Deserialize)]
pub struct AnyFetchAllQueryBody {
query: String,
}
pub async fn any_fetch_all_query(
State(state): State<AppState>,
Json(body): Json<AnyFetchAllQueryBody>,
) -> web::Result<impl IntoResponse> {
let result = state.database.any_fetch_all_query(&body.query).await?;
let result = result.into_iter().map(|row| {
let mut result = serde_json::Map::new();
for column in row.columns().iter() {
let value = row.try_get_raw(column.ordinal()).unwrap();
match value.is_null() {
true => result.insert(
column.name().to_string(),
serde_json::Value::Null,
),
false => {
let json_value = match value.type_info().name() {
"BOOL" => serde_json::Value::Bool(row.try_get(column.ordinal()).unwrap_or_default()),
"INT2" => serde_json::Number::from(row.try_get::<i16, _>(column.ordinal()).unwrap_or_default()).into(),
"INT4" => serde_json::Number::from(row.try_get::<i32, _>(column.ordinal()).unwrap_or_default()).into(),
"INT8" => serde_json::Number::from(row.try_get::<i64, _>(column.ordinal()).unwrap_or_default()).into(),
"TEXT" => serde_json::Value::String(row.try_get::<String, _>(column.ordinal()).unwrap_or_default()),
"VARCHAR" => serde_json::Value::String(row.try_get::<String, _>(column.ordinal()).unwrap_or_default()),
"TIMESTAMPTZ" => serde_json::Value::String(row.try_get::<chrono::DateTime<chrono::Utc>, _>(column.ordinal()).unwrap_or_default().to_rfc3339()),
_ => serde_json::Value::Null,
};
result.insert(
column.name().to_string(),
json_value,
)
},
};
}
result
}).collect::<Vec<_>>();
Ok(axum::response::Json(result))
}

View File

@@ -3,3 +3,4 @@ pub mod message;
pub mod notification;
pub mod secret;
pub mod user;
pub mod lab;