Skip to content

Commit

Permalink
Website: Prompts page (#106)
Browse files Browse the repository at this point in the history
* feat: prompts backend query

* fix: sort order bug

* wipfeat: prompts page

* feat: prompts page
  • Loading branch information
VanillaViking authored Jan 3, 2024
1 parent 7ef8f17 commit 62085da
Show file tree
Hide file tree
Showing 13 changed files with 855 additions and 111 deletions.
51 changes: 35 additions & 16 deletions zyenyo-backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions zyenyo-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
env_logger = "0.10.0"
actix-cors = "0.6.5"
futures = "0.3.30"
64 changes: 21 additions & 43 deletions zyenyo-backend/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,49 @@
use std::env;

mod models;
mod routes;


use models::User;

use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, http};
use actix_web::{web, App, HttpServer};
use actix_cors::Cors;
use mongodb::{Client, Collection, bson::doc};
use serde_json::json;
use mongodb::{Client, Database};
use routes::api_config;

#[derive(Clone)]
pub struct Context {
db: Client,
db: Database,
environment: String,
}

#[get("/api")]
async fn hello() -> impl Responder {
HttpResponse::Ok().json(json!({"ping": "hello world!"}))
}

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}

#[get("/get_user/{discordId}")]
async fn get_user(context: web::Data<Context>, discordId: web::Path<String>) -> HttpResponse {
let discord_id = discordId.into_inner();
let collection: Collection<User> = context.db.database("ZyenyoStaging").collection("usersv2");
println!("{discord_id}");

match collection.find_one(doc! { "discordId": &discord_id }, None).await {
Ok(Some(user)) => HttpResponse::Ok().json(user),
Ok(None) => {
HttpResponse::NotFound().body(format!("No user found with discord ID {discord_id}"))
}
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
env::set_var("RUST_LOG", "debug");
let uri = env::var("MONGO_URI").expect("database URI not provided");
let client = Client::with_uri_str(uri).await.expect("failed to connect to database");
let environment = env::var("ZYENYO_ENVIRONMENT").expect("ENVIRONMENT not provided");

let context = Context {
db: client,
environment,
};

HttpServer::new(move || {
let cors = match context.environment.as_str() {
"development" => Cors::permissive(),
"production" => Cors::default().allowed_origin("https://zyenyobot.com").allowed_methods(vec!["GET", "POST"]),
let mut cors = Cors::permissive();
let context = match environment.as_str() {
"development" => {
Context {
db: client.database("ZyenyoStaging"),
environment: environment.to_owned()
}
},
"production" => {
cors = Cors::default().allowed_origin("https://zyenyobot.com").allowed_methods(vec!["GET", "POST"]);
Context {
db: client.database("MyDatabase"),
environment: environment.to_owned()
}
},
_ => panic!()
};

App::new()
.wrap(cors)
.app_data(web::Data::new(context.clone()))
.service(hello)
.service(echo)
.service(get_user)
.service(web::scope("/api").configure(api_config))
})
.bind("0.0.0.0:8000")?
.run()
Expand Down
7 changes: 7 additions & 0 deletions zyenyo-backend/src/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Prompt {
pub title: String,
pub text: String,
pub rating: f64
}

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct User {
pub discordId: String,
Expand Down
8 changes: 8 additions & 0 deletions zyenyo-backend/src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod prompts;

use actix_web::web;

pub fn api_config(cfg: &mut web::ServiceConfig) {
cfg
.service(prompts::prompts);
}
66 changes: 66 additions & 0 deletions zyenyo-backend/src/routes/prompts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::error::Error;

use actix_web::{get, Responder, HttpResponse, web};
use mongodb::{Collection, bson::{doc, Document, self}};
use serde::Deserialize;
use crate::{Context, models::Prompt};
use futures::stream::TryStreamExt;

const PAGE_DEFAULT: u32 = 1;
const PAGE_SIZE_DEFAULT: u32 = 20;
const SORT_BY_DEFAULT: &str = "title";
const SORT_ORDER_DEFAULT: i32 = 1;
const SEARCH_QUERY_DEFAULT: &str = "";
fn page_default() -> u32 { PAGE_DEFAULT }
fn page_size_default() -> u32 { PAGE_SIZE_DEFAULT }
fn sort_by_default() -> String { SORT_BY_DEFAULT.to_owned() }
fn sort_order_default() -> i32 { SORT_ORDER_DEFAULT }
fn search_query_default() -> String { SEARCH_QUERY_DEFAULT.to_owned() }

#[derive(Deserialize)]
struct PromptsConfig {
#[serde(default = "page_default")]
page: u32,
#[serde(default = "page_size_default")]
page_size: u32,
#[serde(default = "sort_by_default")]
sort_by: String,
#[serde(default = "sort_order_default")]
sort_order: i32,
#[serde(default = "search_query_default")]
search_query: String

}


#[get("/prompts")]
async fn prompts(context: web::Data<Context>, controls: web::Query<PromptsConfig>) -> impl Responder {
let info = controls.into_inner();

match prompt_query(context, info).await {
Ok(prompts_vec) => HttpResponse::Ok().json(prompts_vec),
Err(e) => HttpResponse::InternalServerError().body(e.to_string())
}
}

async fn prompt_query(context: web::Data<Context>, controls: PromptsConfig) -> Result<Vec<Prompt>, Box<dyn Error>> {
let collection: Collection<Prompt> = context.db.collection("prompts");

let pipeline = vec![
doc! {"$match":
doc! {"$expr":
doc! {"$or": [
doc! { "$regexMatch": doc! {"input": "$title", "regex": &controls.search_query, "options": "i"}},
doc! { "$regexMatch": doc! {"input": "$text", "regex": &controls.search_query, "options": "i"}}
] },
}
},
doc! {"$sort": doc! {&controls.sort_by: &controls.sort_order}},
doc! {"$skip": (&controls.page-1)*&controls.page_size},
doc! {"$limit": &controls.page_size}
];

let results = collection.aggregate(pipeline, None).await?;
let prompts_vec: Vec<Document> = results.try_collect().await?;
Ok(prompts_vec.iter().filter_map(|doc| bson::from_document::<Prompt>(doc.to_owned()).ok()).collect())
}
Loading

0 comments on commit 62085da

Please sign in to comment.