Initial, working example.

This commit is contained in:
Imbus 2023-10-09 19:21:55 +02:00
commit f3e5cd62b1
26 changed files with 6597 additions and 0 deletions

1
server/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
/target

2308
server/Cargo.lock generated Executable file

File diff suppressed because it is too large Load diff

17
server/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4.4.0"
clap = { version = "4.4.5", features = ["derive"] }
env_logger = "0.10.0"
log = "0.4.20"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
sled = { version = "0.34.7" }
sqlx = "0.7.2"
uuid = { version = "1.4.1", features = ["serde", "v4"] }

32
server/src/main.rs Executable file
View file

@ -0,0 +1,32 @@
use actix_web::web::Data;
use actix_web::{web::scope, App, HttpServer};
// use uuid::Uuid;
mod routes;
mod types;
use log::info;
use routes::{get_posts, new_post, test};
use types::AppState;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
info!("Starting server...");
let data = AppState::new();
HttpServer::new(move || {
App::new().service(
scope("api")
.service(get_posts)
.service(new_post)
.service(routes::vote)
.service(test)
.app_data(Data::new(data.clone())),
)
})
.bind("localhost:8080")?
.run()
.await
}

88
server/src/routes.rs Executable file
View file

@ -0,0 +1,88 @@
use crate::types::{AppState, NewPost, Post};
use actix_web::web::{Data, Path};
use actix_web::{get, post, web::Json, HttpResponse, Responder};
use log::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[get("/")]
pub async fn get_posts(data: Data<AppState>) -> impl Responder {
match data.posts.lock() {
Ok(posts) => {
let posts: Vec<Post> = posts.values().cloned().collect();
HttpResponse::Ok().json(posts)
}
Err(e) => {
warn!("Error: {:?}", e);
HttpResponse::InternalServerError().body("Error")
}
}
}
#[post("/")]
pub async fn new_post(new_post: Json<NewPost>, data: Data<AppState>) -> impl Responder {
let post = Post::from(new_post.into_inner());
info!("Created post {:?}", post.uuid);
match data.posts.lock() {
Ok(mut posts) => {
posts.insert(post.uuid, post);
}
Err(e) => {
warn!("Error: {:?}", e);
}
};
HttpResponse::Ok().json("Post added!")
}
// This is a test route, returns "Hello, world!"
#[get("/test")]
pub async fn test(data: Data<AppState>) -> impl Responder {
match data.posts.lock() {
Ok(posts) => {
let posts: Vec<Post> = posts.values().cloned().collect();
HttpResponse::Ok().body(format!("Hello, world! {:?}", posts))
}
Err(e) => {
warn!("Error: {:?}", e);
HttpResponse::InternalServerError().body("Error")
}
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum VoteDirection {
Up,
Down,
Unupvote,
Undownvote,
}
#[post("vote/{uuid}/{direction}")]
pub async fn vote(params: Path<(Uuid, VoteDirection)>, data: Data<AppState>) -> impl Responder {
let (uuid, direction) = params.into_inner();
println!("Voting {:?} on post {:?}", direction, uuid);
match data.posts.lock() {
Ok(mut posts) => {
let uuid = uuid;
if let Some(post) = posts.get_mut(&uuid) {
match direction {
VoteDirection::Up => post.votes.up += 1,
VoteDirection::Unupvote => post.votes.up -= 1,
VoteDirection::Down => post.votes.down += 1,
VoteDirection::Undownvote => post.votes.down -= 1,
}
HttpResponse::Ok().body("Downvoted!")
} else {
HttpResponse::NotFound().body("Post not found!")
}
}
Err(e) => {
warn!("Error: {:?}", e);
HttpResponse::InternalServerError().body("Error")
}
}
}

56
server/src/types.rs Executable file
View file

@ -0,0 +1,56 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::Mutex;
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
pub struct NewPost {
title: String,
content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Post {
pub uuid: Uuid,
pub title: String,
pub content: String,
pub votes: VoteCount,
}
impl From<NewPost> for Post {
fn from(post: NewPost) -> Self {
Self {
uuid: Uuid::new_v4(),
title: post.title,
content: post.content,
votes: VoteCount::new(),
}
}
}
#[derive(Clone)]
pub struct AppState {
pub posts: Arc<Mutex<BTreeMap<Uuid, Post>>>,
}
impl AppState {
pub fn new() -> Self {
Self {
posts: Arc::new(Mutex::new(BTreeMap::new())),
}
}
}
// Part of the post struct
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct VoteCount {
pub up: u32,
pub down: u32,
}
impl VoteCount {
fn new() -> Self {
Self { up: 0, down: 0 }
}
}