Initial, working example.
This commit is contained in:
commit
f3e5cd62b1
26 changed files with 6597 additions and 0 deletions
1
server/.gitignore
vendored
Executable file
1
server/.gitignore
vendored
Executable file
|
@ -0,0 +1 @@
|
|||
/target
|
2308
server/Cargo.lock
generated
Executable file
2308
server/Cargo.lock
generated
Executable file
File diff suppressed because it is too large
Load diff
17
server/Cargo.toml
Normal file
17
server/Cargo.toml
Normal 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
32
server/src/main.rs
Executable 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
88
server/src/routes.rs
Executable 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
56
server/src/types.rs
Executable 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 }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue