finish image paths, you can now create, delete, etc. images

This commit is contained in:
SadlyNotSappho 2024-03-01 13:11:09 -08:00
parent 82a42ad0df
commit 0644f034a8
5 changed files with 173 additions and 27 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
/target /target
db/password.txt db/password.txt
.env .env
images/*.png
tmpimages/*.png

View File

@ -14,6 +14,7 @@ services:
target: final target: final
ports: ports:
- 8000:8000 - 8000:8000
user: "${uid}:${gid}"
environment: environment:
- ROCKET_DATABASES={fossil_postgres={url="postgres://postgres:$POSTGRES_PASSWORD@db/$POSTGRES_DB", max_connections=10, connect_timeout=10}} - 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 target: /srv/web
- type: bind - type: bind
source: ./images 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 # 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 # database that your application can use. `depends_on` tells Docker Compose to
# start the database before your application. The `db-data` volume persists the # start the database before your application. The `db-data` volume persists the

View File

@ -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 -> returns the image with the correct filetype - uuid.png would return the image in png format, etc
GET /images/by-user {user: "username"} GET /images/by-user/username DONE
-> gets all of the images made by {user}, if you are {user}, unless you have admin. -> 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 -> 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 -> 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. 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.

View File

@ -5,11 +5,11 @@ use rocket::fs::{FileServer, NamedFile};
use rocket::http::Status; use rocket::http::Status;
use rocket::response::content::{self, RawHtml}; use rocket::response::content::{self, RawHtml};
use rocket::serde::Serialize; use rocket::serde::Serialize;
use rocket::tokio::io::AsyncReadExt;
use rocket::{Build, Request, Rocket}; use rocket::{Build, Request, Rocket};
use rocket_db_pools::sqlx::pool::PoolConnection; use rocket_db_pools::sqlx::pool::PoolConnection;
use rocket_db_pools::sqlx::Postgres; use rocket_db_pools::sqlx::Postgres;
use rocket_db_pools::Connection; use rocket_db_pools::Connection;
use uuid::Uuid;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
#[macro_use] #[macro_use]
@ -24,7 +24,7 @@ use rocket::serde::{json::Json, Deserialize};
use rocket::http::CookieJar; use rocket::http::CookieJar;
use rocket::fs::TempFile; use rocket::fs::TempFile;
use fossil::tables::{Db, Post, User}; use fossil::tables::{Db, Post, User, Image};
#[get("/login")] #[get("/login")]
fn login_page() -> RawHtml<String> { fn login_page() -> RawHtml<String> {
@ -36,6 +36,11 @@ fn createuser_page() -> RawHtml<String> {
RawHtml(fs::read_to_string("/srv/web/createuser.html").unwrap()) RawHtml(fs::read_to_string("/srv/web/createuser.html").unwrap())
} }
#[get("/uploadimage")]
fn uploadimage() -> RawHtml<String> {
RawHtml(fs::read_to_string("/srv/web/uploadimage.html").unwrap())
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
struct LoginInfo { struct LoginInfo {
@ -277,11 +282,13 @@ async fn toggleperms(
} }
#[get("/images/<image>")] #[get("/images/<image>")]
async fn get_image(image: String) -> Result<NamedFile, Status> { async fn get_image(image: String, mut db: Connection<Db>) -> Result<NamedFile, Status> {
let mut split = image.split('.').collect::<Vec<&str>>(); let mut split = image.split('.').collect::<Vec<&str>>();
let format = split.pop().unwrap(); let format = split.pop().unwrap();
let image = split.join("."); 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) => { Ok(i) => {
match i.decode() { match i.decode() {
Ok(img) => { Ok(img) => {
@ -309,21 +316,148 @@ async fn get_image(image: String) -> Result<NamedFile, Status> {
} }
}, },
Err(_) => Err(Status::NotFound) Err(_) => Err(Status::NotFound)
}},
Err(_) => Err(Status::ImATeapot)
} }
} }
#[post("/upload", format = "plain", data = "<file>")] #[post("/upload", format = "image/png", data = "<file>")]
async fn upload(mut file: TempFile<'_>) -> String { async fn upload(mut file: TempFile<'_>, cookies: &CookieJar<'_>, mut db: Connection<Db>) -> String {
eprintln!("{file:?}"); let uuid = Uuid::new_v4().to_string();
let mut content: String = String::new(); match file.copy_to(format!("/srv/tmpimages/{uuid}.png")).await {
file.open().await.unwrap().read_to_string(&mut content).await.unwrap(); Ok(_) => {
eprintln!("{content}"); // validate that it is, in fact, an image
match file.copy_to("/srv/images/file.txt").await { match Reader::open(format!("/srv/tmpimages/{uuid}.png")) {
Ok(_) => String::from("worked"), 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() Err(why) => why.to_string()
} }
} }
#[delete("/images/<uuid>")]
pub async fn delete_image(mut db: Connection<Db>, 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/<username>")]
pub async fn get_images_by_username(mut db: Connection<Db>, cookies: &CookieJar<'_>, username: String) -> Result<Json<Vec<String>>, 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::<Vec<String>>()))
},
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)] #[catch(default)]
fn default_catcher(status: Status, _: &Request) -> RawHtml<String> { fn default_catcher(status: Status, _: &Request) -> RawHtml<String> {
content::RawHtml( content::RawHtml(
@ -426,7 +560,7 @@ use rocket::http::Method;
.attach(cors.to_cors().unwrap()) .attach(cors.to_cors().unwrap())
.mount( .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("/api", routes![api_perms])
.mount("/css", FileServer::from("/srv/web/css")) .mount("/css", FileServer::from("/srv/web/css"))

View File

@ -1,7 +1,6 @@
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use rand::{RngCore, SeedableRng}; use rand::{RngCore, SeedableRng};
use regex::Regex; use regex::Regex;
use rocket::fs::TempFile;
use rocket::http::Cookie; use rocket::http::Cookie;
use rocket_db_pools::sqlx::Executor; use rocket_db_pools::sqlx::Executor;
use rocket_db_pools::Connection; use rocket_db_pools::Connection;
@ -240,17 +239,13 @@ pub struct Image {
} }
impl Image { impl Image {
pub async fn create(db: &mut Connection<Db>, mut image: TempFile<'_>, user: User) -> Status<String> { pub async fn create(db: &mut Connection<Db>, uuid: &String, user: User) -> Status<String> {
let uuid = uuid::Uuid::new_v4().to_string();
image.persist_to(format!("/images/{uuid}.png")).await.unwrap();
match db match db
.execute( .execute(
sqlx::query( sqlx::query(
r#" r#"
INSERT INTO images (uuid, owner_name) INSERT INTO images (uuid, owner_name)
VALUES ($1, $2,); VALUES ($1, $2);
"#, "#,
) )
.bind(&uuid) .bind(&uuid)
@ -260,7 +255,7 @@ impl Image {
{ {
Ok(_) => Status { Ok(_) => Status {
status: StatusTypes::Success, status: StatusTypes::Success,
data: uuid data: "useless field !".to_string()
}, },
Err(why) => { Err(why) => {
eprintln!("Couldn't create database entry: {why:?}"); eprintln!("Couldn't create database entry: {why:?}");
@ -284,6 +279,17 @@ impl Image {
Err(_) => Err("Couldn't get image.".to_string()), Err(_) => Err("Couldn't get image.".to_string()),
} }
} }
pub async fn is_owned_by(db: &mut Connection<Db>, uuid: &String, username: &String) -> Result<bool, String> {
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<Db>, owner_name: &String) -> Result<Vec<Image>, String> { pub async fn get_by_username(db: &mut Connection<Db>, owner_name: &String) -> Result<Vec<Image>, String> {
match db match db
.fetch_all(sqlx::query("SELECT * FROM images WHERE owner_name = $1;").bind(owner_name)) .fetch_all(sqlx::query("SELECT * FROM images WHERE owner_name = $1;").bind(owner_name))