lab
This commit is contained in:
@@ -4,4 +4,4 @@ port = 1234
|
||||
|
||||
[database]
|
||||
max_connections = 5
|
||||
url = "sqlite://nir.db?mode=rwc"
|
||||
url = "postgres://postgres:123456789@localhost:5432/postgres"
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
32
migrations/20250320111939_procedures.sql
Normal file
32
migrations/20250320111939_procedures.sql
Normal 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;
|
||||
$$;
|
||||
127
src/database.rs
127
src/database.rs
@@ -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>;
|
||||
|
||||
@@ -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
63
src/web/routes/lab.rs
Normal 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))
|
||||
}
|
||||
@@ -3,3 +3,4 @@ pub mod message;
|
||||
pub mod notification;
|
||||
pub mod secret;
|
||||
pub mod user;
|
||||
pub mod lab;
|
||||
|
||||
Reference in New Issue
Block a user