Skip to content

Commit

Permalink
feat: refactor bot metadata handling and add votes table
Browse files Browse the repository at this point in the history
Refactor the bot metadata handling to use async/await for improved 
asynchronous performance when fetching data. This change simplifies 
the code by removing blocking calls and enhances the readability of 
the `show` function in `metadata.rs`. 

Additionally, introduce a new `bot_votes` table in the database to 
track votes associated with bots. This includes creating a new 
`BotVote` model to manage vote data effectively. The changes are 
made to support future features related to bot voting.
  • Loading branch information
chikof committed Nov 20, 2024
1 parent b78af17 commit 7647d06
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 39 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "izumo"
version = "0.1.0"
edition = "2021"
build = "build.rs"

# [workspace]
# members = ["oauth"]
Expand Down
15 changes: 15 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// build.rs
use std::env;
use std::fs;
use std::path::Path;

fn main() {
let version = env::var("CARGO_PKG_VERSION").unwrap();
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("version.rs");
fs::write(
dest_path,
format!("pub const VERSION: &str = \"{}\";", version),
)
.unwrap();
}
2 changes: 1 addition & 1 deletion migrations/2024-11-15-152616_add_bot_votes/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ CREATE TABLE bot_votes
(
bot_id VARCHAR NOT NULL REFERENCES bots (id) ON DELETE CASCADE,
date DATE DEFAULT CURRENT_DATE NOT NULL,
votes INTEGER DEFAULT 0 NOT NULL,
votes INTEGER DEFAULT 1 NOT NULL,
PRIMARY KEY (bot_id, date)
);

12 changes: 1 addition & 11 deletions src/controllers/bot/owners.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
use crate::{
app::AppState,
models::Bot,
util::errors::{bot_not_found, AppResult},
views::EncodableBotOwner,
};
use crate::{app::AppState, models::Bot, util::errors::AppResult, views::EncodableBotOwner};
use axum::{extract::Path, Json};
use diesel::OptionalExtension;
use serde_json::Value;

/// Handles `GET /bots/:bot_id/owners` requests.
pub async fn owners(state: AppState, Path(id): Path<String>) -> AppResult<Json<Value>> {
let mut conn = state.db_read().await?;
// spawn_blocking(move || {
// let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();
let id = id.as_str();

let bot: Bot = Bot::find(&mut conn, id).await?;
Expand All @@ -25,6 +17,4 @@ pub async fn owners(state: AppState, Path(id): Path<String>) -> AppResult<Json<V
.collect::<Vec<EncodableBotOwner>>();

Ok(Json(json!({ "users": owners })))
// })
// .await
}
51 changes: 47 additions & 4 deletions src/controllers/bot/votes.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use crate::auth::AuthCheck;
use crate::models::vote::NewBotVote;
use crate::task::spawn_blocking;
use crate::util::errors::bot_not_found;
use crate::views::EncodableBotVote;
use crate::{
app::AppState,
Expand All @@ -6,30 +10,37 @@ use crate::{
util::errors::AppResult,
};
use axum::extract::Path;
use axum::http::request::Parts;
use axum::Json;
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
use serde_json::Value;

/// Handles the `GET /bots/:bot_id/votes` route.
pub async fn votes(app: AppState, Path(bot_id): Path<String>) -> AppResult<Json<Value>> {
let mut conn = app.db_read().await?;

use diesel::dsl::*;
use diesel::prelude::*;
use diesel::sql_types::BigInt;
use diesel_async::RunQueryDsl;

let bot = Bot::find(&mut conn, bot_id.as_str()).await?;

// last 90 votes
let votes = BotVote::belonging_to(&bot)
.filter(bot_votes::date.eq(date(now - 90.days())))
.filter(bot_votes::date.gt(date(now - 90.days())))
.order((bot_votes::date.asc(), bot_votes::bot_id.desc()))
.load(&mut conn)
.await?
.into_iter()
.map(BotVote::into)
.collect::<Vec<EncodableBotVote>>();

let total_votes: i64 = bot_votes::table.count().get_result(&mut conn).await?;
let sum_votes = sql::<BigInt>("SUM(bot_votes.votes)");
let total_votes: i64 = BotVote::belonging_to(&bot)
.select(sum_votes)
.get_result(&mut conn)
.await?;

Ok(Json(json!({
"bot_votes": votes,
Expand All @@ -38,3 +49,35 @@ pub async fn votes(app: AppState, Path(bot_id): Path<String>) -> AppResult<Json<
}
})))
}

// TODO: 12h user ratelimit
/// Handles the `POST /bots/:bot_id/votes` route.
pub async fn vote(
app: AppState,
Path(bot_id): Path<String>,
req: Parts,
) -> AppResult<Json<EncodableBotVote>> {
let conn = app.db_write().await?;
use diesel::OptionalExtension;
use diesel::RunQueryDsl;

spawn_blocking(move || {
let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();

// Make sure user is logged in
let _ = AuthCheck::only_cookie().check(&req, conn)?.user_id();
let bot_id = bot_id.as_str();

// Check if bot exists
let _: Bot = Bot::by_id(bot_id)
.first(conn)
.optional()?
.ok_or_else(|| bot_not_found(bot_id))?;

let new_vote = NewBotVote::new(bot_id);
let vote = new_vote.create(conn)?;

Ok(Json(EncodableBotVote::from(vote)))
})
.await
}
1 change: 0 additions & 1 deletion src/controllers/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::schema::{bots, bots_categories, categories};
use crate::util::errors::AppResult;
use crate::views::{EncodableBot, EncodableCategory};
use axum::Json;
use diesel::prelude::*;
use diesel::{BelongingToDsl, ExpressionMethods, QueryDsl, SelectableHelper};

use diesel_async::AsyncPgConnection;
Expand Down
2 changes: 1 addition & 1 deletion src/models/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl Bot {

#[dsl::auto_type(no_type_alias)]
pub fn by_id(id: &str) -> _ {
bots::table.find(id).select(bots::id)
bots::table.find(id)
}

pub async fn owners(&self, conn: &mut AsyncPgConnection) -> AppResult<Vec<User>> {
Expand Down
37 changes: 36 additions & 1 deletion src/models/vote.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
use crate::diesel::ExpressionMethods;
use crate::models::util::diesel::Conn;
use crate::models::Bot;
use crate::schema::bot_votes;
use chrono::NaiveDate;
use diesel::RunQueryDsl;
use diesel::{QueryResult, SelectableHelper};

#[derive(Queryable, Identifiable, Associations, Debug, Clone)]
#[derive(Queryable, Identifiable, Associations, Selectable, Debug, Clone)]
#[diesel(primary_key(bot_id, date), belongs_to(Bot))]
pub struct BotVote {
pub bot_id: String,
pub date: NaiveDate,
pub votes: i32,
}

#[derive(Insertable, Debug, Clone)]
#[diesel(
table_name = bot_votes,
check_for_backend(diesel::pg::Pg),
)]
pub struct NewBotVote<'a> {
pub bot_id: &'a str,
}

impl<'a> NewBotVote<'a> {
pub fn new(bot_id: &'a str) -> NewBotVote {
Self { bot_id }
}

pub fn create(&self, conn: &mut impl Conn) -> QueryResult<BotVote> {
conn.transaction(|conn| {
use crate::schema::bot_votes::dsl::*;

let vote: BotVote = diesel::insert_into(bot_votes)
.values(self)
.on_conflict((bot_id, date))
.do_update()
.set(votes.eq(votes + 1))
.returning(BotVote::as_returning())
.get_result(conn)?;

Ok(vote)
})
}
}
37 changes: 17 additions & 20 deletions src/router.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
use std::sync::Arc;

use super::controllers::*;
use crate::{
middleware,
util::errors::{not_found, AppResult},
};
use crate::app::{App, AppState};
use crate::middleware;
use crate::util::errors::{not_found, AppResult};
use axum::response::IntoResponse;
use axum::routing::post;
use axum::{
response::IntoResponse,
routing::{delete, get},
Json, Router,
};
use crates_io_env_vars::required_var;
use axum::routing::{delete, get};
use axum::{Json, Router};
use reqwest::{Method, StatusCode};
use serde_json::Value;

use crate::app::{App, AppState};
use std::sync::Arc;

pub fn build_handler(app: Arc<App>) -> axum::Router {
let state = AppState(app);
Expand All @@ -34,9 +27,12 @@ pub fn build_axum_router(state: AppState) -> Router<()> {
.route("/private/session", delete(user::session::logout))
// Bots
.route("/bots/:bot_id", get(bot::metadata::show))
.route("/bots/total_votesnew", post(bot::manage::publish))
.route("/bots/new", post(bot::manage::publish))
.route("/bots/:bot_id/owners", get(bot::owners::owners))
.route("/bots/:bot_id/votes", get(bot::votes::votes))
.route(
"/bots/:bot_id/votes",
get(bot::votes::votes).post(bot::votes::vote),
)
// Categories
.route("/categories", get(category::index))
.route("/categories/:category_id", get(category::show))
Expand All @@ -57,11 +53,12 @@ pub fn build_axum_router(state: AppState) -> Router<()> {
.with_state(state)
}

async fn handler() -> AppResult<Json<Value>> {
let version = required_var("CARGO_PKG_VERSION");
// imports `VERSION` constant
include!(concat!(env!("OUT_DIR"), "/version.rs"));

async fn handler() -> AppResult<Json<Value>> {
Ok(Json(serde_json::json!({
"name": "izumo (api)",
"version": version.unwrap_or("unknown".to_string()),
"name": "Izumo (api)",
"version": VERSION
})))
}

0 comments on commit 7647d06

Please sign in to comment.