finish image paths, you can now create, delete, etc. images
This commit is contained in:
parent
82a42ad0df
commit
0644f034a8
|
@ -1,3 +1,5 @@
|
||||||
/target
|
/target
|
||||||
db/password.txt
|
db/password.txt
|
||||||
.env
|
.env
|
||||||
|
images/*.png
|
||||||
|
tmpimages/*.png
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
||||||
|
@ -32,6 +33,9 @@ services:
|
||||||
- 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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
160
src/main.rs
160
src/main.rs
|
@ -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"))
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue