From 0644f034a8e42773b211556a8410eb625f0cecc7 Mon Sep 17 00:00:00 2001 From: SadlyNotSappho Date: Fri, 1 Mar 2024 13:11:09 -0800 Subject: [PATCH] finish image paths, you can now create, delete, etc. images --- .gitignore | 2 + docker-compose.yml | 6 +- imagesplan.txt | 10 +-- src/main.rs | 160 +++++++++++++++++++++++++++++++++++++++++---- src/tables.rs | 22 ++++--- 5 files changed, 173 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 304cedc..e4b1af8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target db/password.txt .env +images/*.png +tmpimages/*.png diff --git a/docker-compose.yml b/docker-compose.yml index 047630e..65d8e24 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,7 @@ services: target: final ports: - 8000:8000 + user: "${uid}:${gid}" environment: - ROCKET_DATABASES={fossil_postgres={url="postgres://postgres:$POSTGRES_PASSWORD@db/$POSTGRES_DB", max_connections=10, connect_timeout=10}} @@ -31,7 +32,10 @@ services: target: /srv/web - type: bind source: ./images - target: /srv/images + target: /srv/images + - type: bind + source: ./tmpimages + target: /srv/tmpimages # The commented out section below is an example of how to define a PostgreSQL # database that your application can use. `depends_on` tells Docker Compose to # start the database before your application. The `db-data` volume persists the diff --git a/imagesplan.txt b/imagesplan.txt index 59c3fa6..b8f9058 100644 --- a/imagesplan.txt +++ b/imagesplan.txt @@ -1,13 +1,13 @@ -GET /images/uuid.filetype +GET /images/uuid.filetype DONE -> returns the image with the correct filetype - uuid.png would return the image in png format, etc -GET /images/by-user {user: "username"} - -> gets all of the images made by {user}, if you are {user}, unless you have admin. +GET /images/by-user/username DONE + -> gets all of the images made by {username}, if you are {username}, or if you have admin. -POST /images/create {image: "image data"} +POST /images/create {image: "image data"} DONE -> returns the uuid of the image, which it saves to the folder and database -DELETE /images/uuid +DELETE /images/uuid DONE -> if you're the owner of the image or an admin, deletes the image. returns basic success/faliure images are stored in /images/uuid.png. Image::get_by_uuid(uuid) just gets the image from the folder, verifies that it is an image, and returns it. Image::get_by_username(username) gets all database images from the database and returns the uuids. the client is responsible for getting the images. diff --git a/src/main.rs b/src/main.rs index 8cc187c..11b73d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,11 @@ use rocket::fs::{FileServer, NamedFile}; use rocket::http::Status; use rocket::response::content::{self, RawHtml}; use rocket::serde::Serialize; -use rocket::tokio::io::AsyncReadExt; use rocket::{Build, Request, Rocket}; use rocket_db_pools::sqlx::pool::PoolConnection; use rocket_db_pools::sqlx::Postgres; use rocket_db_pools::Connection; +use uuid::Uuid; use std::fs; use std::path::Path; #[macro_use] @@ -24,7 +24,7 @@ use rocket::serde::{json::Json, Deserialize}; use rocket::http::CookieJar; use rocket::fs::TempFile; -use fossil::tables::{Db, Post, User}; +use fossil::tables::{Db, Post, User, Image}; #[get("/login")] fn login_page() -> RawHtml { @@ -36,6 +36,11 @@ fn createuser_page() -> RawHtml { RawHtml(fs::read_to_string("/srv/web/createuser.html").unwrap()) } +#[get("/uploadimage")] +fn uploadimage() -> RawHtml { + RawHtml(fs::read_to_string("/srv/web/uploadimage.html").unwrap()) +} + #[derive(Deserialize)] #[serde(crate = "rocket::serde")] struct LoginInfo { @@ -277,11 +282,13 @@ async fn toggleperms( } #[get("/images/")] -async fn get_image(image: String) -> Result { +async fn get_image(image: String, mut db: Connection) -> Result { let mut split = image.split('.').collect::>(); let format = split.pop().unwrap(); let image = split.join("."); - match Reader::open(format!("/srv/images/{image}.png")) { + + match Image::get_by_uuid(&mut db, &image).await { + Ok(_) => {match Reader::open(format!("/srv/images/{image}.png")) { Ok(i) => { match i.decode() { Ok(img) => { @@ -309,21 +316,148 @@ async fn get_image(image: String) -> Result { } }, Err(_) => Err(Status::NotFound) + }}, + Err(_) => Err(Status::ImATeapot) } } -#[post("/upload", format = "plain", data = "")] -async fn upload(mut file: TempFile<'_>) -> String { - eprintln!("{file:?}"); - let mut content: String = String::new(); - file.open().await.unwrap().read_to_string(&mut content).await.unwrap(); - eprintln!("{content}"); - match file.copy_to("/srv/images/file.txt").await { - Ok(_) => String::from("worked"), +#[post("/upload", format = "image/png", data = "")] +async fn upload(mut file: TempFile<'_>, cookies: &CookieJar<'_>, mut db: Connection) -> String { + let uuid = Uuid::new_v4().to_string(); + match file.copy_to(format!("/srv/tmpimages/{uuid}.png")).await { + Ok(_) => { + // validate that it is, in fact, an image + match Reader::open(format!("/srv/tmpimages/{uuid}.png")) { + Ok(_) => { + match cookies.get_private("token") { + Some(t) => { + match User::get_by_token(&mut db, t).await { + Some(user) => { + if user.make_posts == true || user.admin == true { + // upload to db + match Image::create(&mut db, &uuid, user).await.status { + StatusTypes::Success => { + // move image + match file.copy_to(format!("/srv/images/{uuid}.png")).await { + Ok(_) => { + match fs::remove_file(format!("/srv/tmpimages/{uuid}.png")) { + Ok(_) => { + uuid + }, + Err(why) => { + eprintln!("{why:?}"); + "couldn't delete old file".to_string() + } + } + }, + Err(why) => { + eprintln!("{why:?}"); + "couldn't save file to final destination".to_string() + } + } + }, + StatusTypes::Faliure => { + "Couldn't save to DB".to_string() + } + } + } else { + "Invalid perms".to_string() + } + }, + None => { + "Invalid login token".to_string() + } + } + }, + None => { + "Not logged in".to_string() + } + } + }, + Err(why) => { + // isn't an image, or something else went wrong + eprintln!("{why:?}"); + "error".to_string() + } + } + }, Err(why) => why.to_string() } } +#[delete("/images/")] +pub async fn delete_image(mut db: Connection, cookies: &CookieJar<'_>, uuid: String) -> String { + let token = cookies.get_private("token"); + match token { + Some(t) => { + match User::get_by_token(&mut db, t).await { + Some(user) => { + match Image::is_owned_by(&mut db, &uuid, &user.username).await { + Ok(b) => { + match b { + true => { + match Image::delete(&mut db, &uuid).await.status { + StatusTypes::Success => { + match fs::remove_file(format!("/srv/images/{uuid}.png")) { + Ok(_) => { + "deleted!".to_string() + }, + Err(why) => { + eprintln!("{why:?}"); + "Image deleted from database but not filesystem".to_string() + } + } + }, + StatusTypes::Faliure => "Couldn't delete image".to_string() + } + }, + false => "You don't own that image".to_string() + } + }, + Err(_) => "Couldn't get image".to_string() + } + }, + None => "Invalid login token".to_string() + } + }, + None => { + "Not logged in".to_string() + } + } +} + +#[get("/images/by-user/")] +pub async fn get_images_by_username(mut db: Connection, cookies: &CookieJar<'_>, username: String) -> Result>, String> { + let token = cookies.get_private("token"); + match token { + Some(t) => { + match User::get_by_token(&mut db, t).await { + Some(user) => { + match user.admin || user.username == username { + true => { + match Image::get_by_username(&mut db, &username).await { + Ok(images) => { + Ok(Json::from(images.into_iter().map(|i| i.uuid).collect::>())) + }, + Err(why) => { + eprintln!("{why:?}"); + Err("Couldn't get that user's images".to_string()) + } + } + + }, + false => Err("You don't have permission to do this".to_string()) + } + }, + None => Err("Invalid login token".to_string()) + } + }, + None => { + Err("Not logged in".to_string()) + } + } +} + #[catch(default)] fn default_catcher(status: Status, _: &Request) -> RawHtml { content::RawHtml( @@ -426,7 +560,7 @@ use rocket::http::Method; .attach(cors.to_cors().unwrap()) .mount( "/", - routes![login_page, login, logout, createuser, createuser_page, account, adminpanel, toggleperms, get_image, upload], + routes![login_page, login, logout, createuser, createuser_page, account, adminpanel, toggleperms, get_image, upload, uploadimage, delete_image, get_images_by_username], ) .mount("/api", routes![api_perms]) .mount("/css", FileServer::from("/srv/web/css")) diff --git a/src/tables.rs b/src/tables.rs index 96cc510..7ea43f3 100644 --- a/src/tables.rs +++ b/src/tables.rs @@ -1,7 +1,6 @@ use base64::{engine::general_purpose, Engine}; use rand::{RngCore, SeedableRng}; use regex::Regex; -use rocket::fs::TempFile; use rocket::http::Cookie; use rocket_db_pools::sqlx::Executor; use rocket_db_pools::Connection; @@ -240,17 +239,13 @@ pub struct Image { } impl Image { - pub async fn create(db: &mut Connection, mut image: TempFile<'_>, user: User) -> Status { - let uuid = uuid::Uuid::new_v4().to_string(); - - image.persist_to(format!("/images/{uuid}.png")).await.unwrap(); - + pub async fn create(db: &mut Connection, uuid: &String, user: User) -> Status { match db .execute( sqlx::query( r#" INSERT INTO images (uuid, owner_name) - VALUES ($1, $2,); + VALUES ($1, $2); "#, ) .bind(&uuid) @@ -260,7 +255,7 @@ impl Image { { Ok(_) => Status { status: StatusTypes::Success, - data: uuid + data: "useless field !".to_string() }, Err(why) => { eprintln!("Couldn't create database entry: {why:?}"); @@ -284,6 +279,17 @@ impl Image { Err(_) => Err("Couldn't get image.".to_string()), } } + pub async fn is_owned_by(db: &mut Connection, uuid: &String, username: &String) -> Result { + match Image::get_by_uuid(db, &uuid).await { + Ok(img) => { + Ok(&img.owner_name == username) + }, + Err(why) => { + eprintln!("{why:?}"); + Err("couldn't get image".to_string()) + } + } + } pub async fn get_by_username(db: &mut Connection, owner_name: &String) -> Result, String> { match db .fetch_all(sqlx::query("SELECT * FROM images WHERE owner_name = $1;").bind(owner_name))