From 9e92335337d532e88bcaac65a30a82b319ed8431 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Mon, 9 Dec 2024 20:21:39 -0500 Subject: [PATCH 01/43] Begin rust rewrite --- backend/.gitignore | 40 --- backend/Dockerfile | 10 - backend/build.gradle.kts | 62 ----- backend/gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - backend/gradlew | 252 ------------------ backend/gradlew.bat | 94 ------- backend/settings.gradle.kts | 1 - backend/src/aws_service.rs | 82 ++++++ backend/src/controllers/get_controller.rs | 84 ++++++ backend/src/controllers/mod.rs | 2 + backend/src/controllers/upload_controller.rs | 101 +++++++ backend/src/delete_service.rs | 90 +++++++ .../dev/loudbook/pastebook/BucketUtils.kt | 23 -- .../dev/loudbook/pastebook/ContentScanner.kt | 25 -- .../dev/loudbook/pastebook/DeleteHandler.kt | 63 ----- .../kotlin/dev/loudbook/pastebook/IPUtils.kt | 19 -- .../pastebook/PasteBookApplication.kt | 14 - .../pastebook/config/Configuration.kt | 11 - .../loudbook/pastebook/config/WebConfig.kt | 13 - .../pastebook/controllers/GetController.kt | 97 ------- .../pastebook/controllers/UploadController.kt | 89 ------- .../dev/loudbook/pastebook/data/PasteDTO.kt | 6 - .../pastebook/data/PastePrivateDTO.kt | 8 - .../dev/loudbook/pastebook/data/R2Service.kt | 72 ----- .../dev/loudbook/pastebook/data/User.kt | 8 - .../dev/loudbook/pastebook/data/UserDTO.kt | 3 - .../pastebook/mongo/MigrationHandler.kt | 63 ----- .../pastebook/mongo/PasteRepository.kt | 13 - .../loudbook/pastebook/mongo/UserService.kt | 40 --- .../pastebook/mongo/UsersRepository.kt | 10 - backend/src/main/resources/application.yml | 15 -- backend/src/models/mod.rs | 2 + backend/src/models/paste.rs | 38 +++ backend/src/models/user.rs | 29 ++ backend/src/mongodb_service.rs | 80 ++++++ backend/src/mongoresult.rs | 3 + .../pastebook/PasteBookApplicationTests.kt | 13 - backend/src/utils/iputils.rs | 22 ++ backend/src/utils/mod.rs | 1 + 40 files changed, 534 insertions(+), 1071 deletions(-) delete mode 100644 backend/.gitignore delete mode 100644 backend/Dockerfile delete mode 100644 backend/build.gradle.kts delete mode 100644 backend/gradle/wrapper/gradle-wrapper.jar delete mode 100644 backend/gradle/wrapper/gradle-wrapper.properties delete mode 100755 backend/gradlew delete mode 100644 backend/gradlew.bat delete mode 100644 backend/settings.gradle.kts create mode 100644 backend/src/aws_service.rs create mode 100644 backend/src/controllers/get_controller.rs create mode 100644 backend/src/controllers/mod.rs create mode 100644 backend/src/controllers/upload_controller.rs create mode 100644 backend/src/delete_service.rs delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/BucketUtils.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/ContentScanner.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/DeleteHandler.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/IPUtils.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/PasteBookApplication.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/config/Configuration.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/config/WebConfig.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/controllers/GetController.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/controllers/UploadController.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/data/PasteDTO.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/data/PastePrivateDTO.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/data/R2Service.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/data/User.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/data/UserDTO.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/mongo/MigrationHandler.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/mongo/PasteRepository.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UserService.kt delete mode 100644 backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UsersRepository.kt delete mode 100644 backend/src/main/resources/application.yml create mode 100644 backend/src/models/mod.rs create mode 100644 backend/src/models/paste.rs create mode 100644 backend/src/models/user.rs create mode 100644 backend/src/mongodb_service.rs create mode 100644 backend/src/mongoresult.rs delete mode 100644 backend/src/test/kotlin/dev/loudbook/pastebook/pastebook/PasteBookApplicationTests.kt create mode 100644 backend/src/utils/iputils.rs create mode 100644 backend/src/utils/mod.rs diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 44d2e86..0000000 --- a/backend/.gitignore +++ /dev/null @@ -1,40 +0,0 @@ -HELP.md -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Kotlin ### -.kotlin \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 86a4f1b..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ - -FROM openjdk:23-slim - -WORKDIR /pastebook-backend - -COPY build/libs/pastebook.jar pastebook.jar - -EXPOSE 8080 - -ENTRYPOINT ["java", "-jar", "pastebook.jar"] \ No newline at end of file diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts deleted file mode 100644 index 0ffff89..0000000 --- a/backend/build.gradle.kts +++ /dev/null @@ -1,62 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("org.springframework.boot") version "3.4.0" - id("io.spring.dependency-management") version "1.1.6" - kotlin("jvm") version "2.1.0" - kotlin("plugin.spring") version "2.1.0" -} -val springCloudVersion by extra("2023.0.1") - -group = "dev.loudbook" -version = "0.0.1-SNAPSHOT" - -java { - sourceCompatibility = JavaVersion.VERSION_23 - targetCompatibility = JavaVersion.VERSION_23 -} - -repositories { - mavenCentral() -} - -dependencies { - implementation("org.springframework.boot:spring-boot-starter-data-mongodb") - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("com.github.vladimir-bukhtoyarov:bucket4j-core:8.0.1") - testImplementation("org.springframework.boot:spring-boot-starter-test") - implementation("com.google.code.gson:gson:2.11.0") - implementation("me.paulschwarz:spring-dotenv:4.0.0") - implementation("commons-validator:commons-validator:1.8.0") - implementation("software.amazon.awssdk:s3:2.29.29") -} - -dependencyManagement { - imports { - mavenBom("org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion") - } -} - -tasks.withType { - compilerOptions { - freeCompilerArgs.add("-Xjsr305=strict") - jvmTarget.set(JvmTarget.JVM_23) - } -} - -tasks.bootJar { - archiveBaseName.set("pastebook") - archiveVersion.set("") - archiveClassifier.set("") -} - -tasks.withType { - useJUnitPlatform() -} - -configurations.implementation { - exclude(group = "commons-logging", module = "commons-logging") -} \ No newline at end of file diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9530d66f5e68d973ea569d8e19de379189..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e2847c8..0000000 --- a/backend/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew deleted file mode 100755 index f5feea6..0000000 --- a/backend/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat deleted file mode 100644 index 9d21a21..0000000 --- a/backend/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/backend/settings.gradle.kts b/backend/settings.gradle.kts deleted file mode 100644 index 0c750f3..0000000 --- a/backend/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "PasteBook" diff --git a/backend/src/aws_service.rs b/backend/src/aws_service.rs new file mode 100644 index 0000000..50c02d9 --- /dev/null +++ b/backend/src/aws_service.rs @@ -0,0 +1,82 @@ +use anyhow::Result; +use aws_sdk_s3::config::{Credentials, Region, SharedCredentialsProvider}; +use aws_sdk_s3::primitives::ByteStream; +use aws_sdk_s3::{Client, Config}; + +pub struct AWSService { + client: Client, + bucket_name: String, +} + +impl AWSService { + pub async fn new(endpoint: &str, bucket_name: &str, access_key: &str, secret_key: &str) -> Result { + let region = Region::new("auto"); + let credentials = Credentials::from_keys(access_key, secret_key, None); + let shared_credentials = SharedCredentialsProvider::new(credentials); + + let config = Config::builder() + .region(region) + .credentials_provider(shared_credentials) + .endpoint_url(endpoint) + .build(); + + let client = Client::from_conf(config); + + Ok(Self { + client, + bucket_name: bucket_name.to_string(), + }) + } + + pub async fn get_file(&self, key: &str) -> Result> { + let resp = self + .client + .get_object() + .bucket(&self.bucket_name) + .key(key) + .send() + .await?; + + let data = resp.body.collect().await?; + Ok(data.into_bytes().to_vec()) + } + + pub async fn put_file(&self, key: &str, data: &[u8]) -> Result<()> { + self.client + .put_object() + .bucket(&self.bucket_name) + .key(key) + .body(ByteStream::from(data.to_vec())) + .send() + .await?; + Ok(()) + } + + pub async fn delete_file(&self, key: &str) -> Result<()> { + self.client + .delete_object() + .bucket(&self.bucket_name) + .key(key) + .send() + .await?; + Ok(()) + } + + pub async fn list_files(&self) -> Result> { + let resp = self + .client + .list_objects_v2() + .bucket(&self.bucket_name) + .send() + .await?; + + let mut keys = Vec::new(); + for object in resp.contents.unwrap_or_default() { + if let Some(key) = object.key { + keys.push(key); + } + } + + Ok(keys) + } +} diff --git a/backend/src/controllers/get_controller.rs b/backend/src/controllers/get_controller.rs new file mode 100644 index 0000000..bcc6e9e --- /dev/null +++ b/backend/src/controllers/get_controller.rs @@ -0,0 +1,84 @@ +use crate::aws_service::AWSService; +use actix_web::{ + web, HttpRequest, HttpResponse, Responder, +}; +use serde::Deserialize; +use std::sync::Arc; +use crate::mongodb_service::MongoService; + +#[derive(Deserialize)] +pub struct ContentQuery { + pub compress: Option, +} + +pub async fn get_metadata_handler( + mongo_service: web::Data>, + request: HttpRequest, + path: web::Path, +) -> impl Responder { + let ip = extract_ip(&request); + if mongo_service.is_user_banned(&ip).await.expect("Failed to check if user is banned") { + return HttpResponse::Forbidden().body("Prohibited"); + } + + match mongo_service.get_paste_metadata(&path).await { + Ok(Some(metadata)) => { + let user = mongo_service.get_user(&metadata.creator_ip).await.unwrap(); + + let public_dto = metadata.to_public_dto(user.unwrap().to_dto()); + HttpResponse::Ok().json(public_dto) + } + Ok(None) => HttpResponse::NotFound().body("Not Found"), + Err(_) => HttpResponse::InternalServerError().body("Internal Server Error"), + } +} + +pub async fn get_content_handler( + aws_service: web::Data>, + mongo_service: web::Data>, + request: HttpRequest, + path: web::Path, + query: web::Query, +) -> impl Responder { + let compress = query.compress.unwrap_or(true); + let ip = extract_ip(&request); + + if mongo_service.is_user_banned(&ip).await.expect("Failed to check if user is banned") { + return HttpResponse::Forbidden().body("Prohibited"); + } + + mongo_service.increment_requests(&ip).await.expect("Failed to increment requests"); + + match aws_service.get_file(&path).await { + Ok(data) => { + if compress { + let compressed = compress_data(&data); + HttpResponse::Ok() + .content_type("text/plain; charset=utf-8") + .append_header(("Content-Encoding", "gzip")) + .body(compressed) + } else { + HttpResponse::Ok() + .content_type("text/plain; charset=utf-8") + .body(data) + } + } + Err(_) => HttpResponse::NotFound().body("File not found"), + } +} + +fn compress_data(data: &[u8]) -> Vec { + use flate2::write::GzEncoder; + use flate2::Compression; + use std::io::Write; + + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(data).expect("Failed to write data"); + encoder.finish().expect("Failed to finish compression") +} + +fn extract_ip(req: &HttpRequest) -> String { + req.peer_addr() + .map(|addr| addr.ip().to_string()) + .unwrap_or_else(|| "unknown".to_string()) +} diff --git a/backend/src/controllers/mod.rs b/backend/src/controllers/mod.rs new file mode 100644 index 0000000..a2b619e --- /dev/null +++ b/backend/src/controllers/mod.rs @@ -0,0 +1,2 @@ +pub mod get_controller; +pub mod upload_controller; \ No newline at end of file diff --git a/backend/src/controllers/upload_controller.rs b/backend/src/controllers/upload_controller.rs new file mode 100644 index 0000000..1edc330 --- /dev/null +++ b/backend/src/controllers/upload_controller.rs @@ -0,0 +1,101 @@ +use crate::aws_service::AWSService; +use crate::models::paste::Paste; +use crate::mongodb_service::MongoService; +use crate::utils::iputils::IPUtils; +use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +pub async fn upload_handler( + aws_service: web::Data>, + mongo_service: web::Data>, + req: HttpRequest, + body: String, +) -> impl Responder { + let title = req + .headers() + .get("title") + .and_then(|v| v.to_str().ok()) + .unwrap_or(""); + if title.is_empty() { + return HttpResponse::BadRequest().body("Title is required"); + } + + let report_book = req + .headers() + .get("reportBook") + .map(|v| v.to_str().unwrap_or("false") == "true") + .unwrap_or(false); + + let wrap = req + .headers() + .get("wrap") + .map(|v| v.to_str().unwrap_or("false") == "true") + .unwrap_or(false); + + let mut expires = req + .headers() + .get("expires") + .and_then(|v| v.to_str().ok()?.parse::().ok()) + .unwrap_or_else(|| 86_400_000); + + let since_the_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + if expires < 60_000 { + return HttpResponse::BadRequest().body("Expire time too short"); + } + if expires < since_the_epoch { + expires += since_the_epoch; + } + if expires > since_the_epoch + 2_765_000_000 { + return HttpResponse::BadRequest().body("Expire time too long"); + } + + let ip = match IPUtils::get_ip_from_request(&req) { + Some(ip) => ip, + None => return HttpResponse::BadRequest().body("Failed to get IP"), + }; + + let file_id = generate_random_string(5); + + let paste = Paste { + id: Some(file_id.clone()), + title: title.to_string(), + created: since_the_epoch, + report_book, + wrap, + creator_ip: ip.clone(), + expires, + }; + + if let Err(e) = aws_service.put_file(&file_id, (&body).as_ref()).await { + return HttpResponse::InternalServerError().body(format!("Failed to upload file: {:?}", e)); + } + if let Err(e) = mongo_service.put_paste(paste).await { + return HttpResponse::InternalServerError().body(format!("Failed to save to database: {:?}", e)); + } + + let host_domain = req + .headers() + .get("X-Domain-Name") + .and_then(|v| v.to_str().ok()); + let response_body = if let Some(domain) = host_domain { + format!("https://{}/p/{}", domain, file_id) + } else { + file_id.clone() + }; + + HttpResponse::Ok().body(response_body) +} + +fn generate_random_string(length: usize) -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(length) + .map(char::from) + .collect() +} diff --git a/backend/src/delete_service.rs b/backend/src/delete_service.rs new file mode 100644 index 0000000..c101f21 --- /dev/null +++ b/backend/src/delete_service.rs @@ -0,0 +1,90 @@ +use std::sync::Arc; +use std::time::Duration; +use chrono::Utc; +use futures_util::StreamExt; +use log::{error, warn}; +use tokio::spawn; +use tokio::time::interval; +use crate::aws_service::AWSService; +use crate::mongodb_service::MongoService; + +pub struct DeleteHandler { + aws_service: Arc, + mongo_service: Arc, +} + +impl DeleteHandler { + pub fn new(aws_service: Arc, mongo_service: Arc) -> Self { + let handler = Self { + aws_service, + mongo_service, + }; + + handler.start_delete_loop(); + handler + } + + pub(crate) fn start_delete_loop(&self) { + let aws_service = Arc::clone(&self.aws_service); + let mongo_service = Arc::clone(&self.mongo_service); + + spawn(async move { + let mut interval = interval(Duration::from_secs(600)); + loop { + interval.tick().await; + if let Err(err) = Self::delete_files(&aws_service, &mongo_service).await { + error!("Error during deletion process: {}", err); + } + } + }); + } + + async fn delete_files( + aws_service: &Arc, + mongo_service: &Arc, + ) -> Result<(), String> { + let now = Utc::now().timestamp_millis(); + let mut deletable_pastes = Vec::new(); + let mut all_pastes = Vec::new(); + + if let Ok(mut pastes_cursor) = mongo_service.get_all_pastes_metadata().await { + while let Some(paste) = pastes_cursor.next().await { + let paste = paste.expect("Failed to get paste"); + + if paste.expires > 0 && paste.expires < now as u64 { + deletable_pastes.push(paste.clone()); + } + + all_pastes.push(paste); + } + } + + for paste in &deletable_pastes { + let paste_id = paste.id.as_deref().unwrap_or_default(); + if let Err(err) = mongo_service.delete_paste(paste_id).await { + error!("Failed to delete paste from database: {:?}", err); + } + if let Some(id) = &paste.id { + if let Err(err) = mongo_service.delete_paste(id).await { + error!("Failed to delete paste file: {}", err); + } + } else { + error!("Paste ID is missing for: {:?}", paste); + } + } + + if let Ok(file_names) = aws_service.list_files().await { + for file_name in file_names { + if all_pastes.iter().all(|paste| paste.id.as_deref() != Some(&file_name)) { + if let Err(err) = aws_service.delete_file(&file_name).await { + error!("Failed to delete invalid file {}: {}", file_name, err); + } else { + warn!("Deleted invalid file: {}", file_name); + } + } + } + } + + Ok(()) + } +} diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/BucketUtils.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/BucketUtils.kt deleted file mode 100644 index 522de61..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/BucketUtils.kt +++ /dev/null @@ -1,23 +0,0 @@ -package dev.loudbook.pastebook - -import io.github.bucket4j.Bandwidth -import io.github.bucket4j.Bucket -import io.github.bucket4j.Refill -import java.time.Duration - - -object BucketUtils { - fun getBucketPerSeconds(perSeconds: Long): Bucket { - val limit = Bandwidth.classic(perSeconds, Refill.greedy(perSeconds, Duration.ofSeconds(1))) - return Bucket.builder() - .addLimit(limit) - .build() - } - - fun getBucketPerMinutes(perMinutes: Long): Bucket { - val limit = Bandwidth.classic(perMinutes, Refill.greedy(perMinutes, Duration.ofMinutes(1))) - return Bucket.builder() - .addLimit(limit) - .build() - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/ContentScanner.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/ContentScanner.kt deleted file mode 100644 index 4721da5..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/ContentScanner.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.loudbook.pastebook - -object ContentScanner { - fun scanContent(content: String): String { -/* val lines = content.split("\n").toMutableList() - - for ((index, line) in lines.withIndex()) { - val words = line.split(" ").toMutableList() - - for ((wordIndex, word) in words.withIndex()) { - if (InetAddressValidator.getInstance().isValidInet4Address(word)) { - words[wordIndex] = "***.***.***.***" - } else if (InetAddressValidator.getInstance().isValidInet6Address(word)) { - words[wordIndex] = "****:****:****:****:****:****:****:****" - } - } - - lines[index] = words.joinToString(" ") - } - - return lines.joinToString("\n")*/ - - return content - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/DeleteHandler.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/DeleteHandler.kt deleted file mode 100644 index 24f9a11..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/DeleteHandler.kt +++ /dev/null @@ -1,63 +0,0 @@ -package dev.loudbook.pastebook - -import dev.loudbook.pastebook.data.PastePrivateDTO -import dev.loudbook.pastebook.data.R2Service -import dev.loudbook.pastebook.mongo.PasteRepository -import jakarta.annotation.PostConstruct -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component -import kotlin.concurrent.fixedRateTimer - -@Component -class DeleteHandler { - @Autowired - lateinit var r2Service: R2Service - - @Autowired - lateinit var pasteRepository: PasteRepository - - private val logger: Logger = LoggerFactory.getLogger(DeleteHandler::class.java) - - @PostConstruct - fun init() { - beginLoop() - } - - private final fun beginLoop() { - fixedRateTimer("timer", true, 1000, 1000 * 60 * 10) { - deleteFiles() - } - } - - private fun deleteFiles() { - val deletablePastes = mutableListOf() - val allPastes = mutableListOf() - - var index = 0 - - pasteRepository.findAllDTO().forEach { - val expires = it.expires - index++ - - allPastes.add(it) - - if (System.currentTimeMillis() > expires && expires != 0L) { - deletablePastes.add(it) - } - } - - for (paste in deletablePastes) { - pasteRepository.delete(paste) - paste.id?.let { r2Service.deleteFile(it) } ?: logger.error("Failed to delete paste; $paste") - } - - for (listFileName in r2Service.listFileNames()) { - if (allPastes.none{ it.id == listFileName}) { - r2Service.deleteFile(listFileName) - logger.warn("Deleted invalid file $listFileName") - } - } - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/IPUtils.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/IPUtils.kt deleted file mode 100644 index 5def367..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/IPUtils.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.loudbook.pastebook - -import jakarta.servlet.http.HttpServletRequest - -object IPUtils { - fun getIPFromRequest(request: HttpServletRequest): String? { - var xRealIP = request.getHeader("Cf-Connecting-IP") - - if (xRealIP == null) { - xRealIP = request.getHeader("CF-Connecting-IPv6") - } - - if (xRealIP == null) { - return request.remoteAddr - } - - return xRealIP.split(",")[0] - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/PasteBookApplication.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/PasteBookApplication.kt deleted file mode 100644 index 8a42001..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/PasteBookApplication.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.loudbook.pastebook - -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.data.mongodb.repository.config.EnableMongoRepositories - -@SpringBootApplication -@EnableMongoRepositories -class PasteBookApplication - -fun main(args: Array) { - val application = SpringApplication(PasteBookApplication::class.java) - application.run(*args) -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/config/Configuration.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/config/Configuration.kt deleted file mode 100644 index 7a39ce4..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/config/Configuration.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.loudbook.pastebook.config - -import dev.loudbook.pastebook.DeleteHandler -import org.springframework.context.annotation.Bean -import org.springframework.stereotype.Component - -@Component -class Configuration { - @Bean - fun deleteHandler() = DeleteHandler() -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/config/WebConfig.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/config/WebConfig.kt deleted file mode 100644 index d8cb93a..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/config/WebConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.loudbook.pastebook.config -import org.springframework.context.annotation.Configuration -import org.springframework.web.servlet.config.annotation.CorsRegistry -import org.springframework.web.servlet.config.annotation.EnableWebMvc -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer - -@Configuration -@EnableWebMvc -class WebConfig : WebMvcConfigurer { - override fun addCorsMappings(registry: CorsRegistry) { - registry.addMapping("/**") - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/controllers/GetController.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/controllers/GetController.kt deleted file mode 100644 index 6239b0c..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/controllers/GetController.kt +++ /dev/null @@ -1,97 +0,0 @@ -package dev.loudbook.pastebook.controllers - -import com.google.gson.Gson -import dev.loudbook.pastebook.BucketUtils -import dev.loudbook.pastebook.data.R2Service -import dev.loudbook.pastebook.data.PastePrivateDTO -import dev.loudbook.pastebook.mongo.PasteRepository -import dev.loudbook.pastebook.mongo.UserService -import io.github.bucket4j.Bucket -import jakarta.servlet.http.HttpServletRequest -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.HttpHeaders -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.io.ByteArrayOutputStream -import java.util.zip.GZIPOutputStream - -@RestController -class GetController { - @Autowired - lateinit var r2Service: R2Service - - @Autowired - lateinit var pasteRepository: PasteRepository - - @Autowired - lateinit var userService: UserService - - private val bucket: Bucket = BucketUtils.getBucketPerSeconds(4) - - @GetMapping("/get/{id}/metadata") - fun get(@PathVariable id: String, request: HttpServletRequest): ResponseEntity { - if (!userService.processRequest(request)) { - return ResponseEntity.status(403).body("Prohibited") - } - - if (!bucket.tryConsume(1)) { - return ResponseEntity.status(429).body("Rate limit exceeded") - } - - val paste: PastePrivateDTO = pasteRepository.findDTOByID(id) ?: return ResponseEntity.notFound().build() - - val json = Gson().toJson(userService.getUser(paste.creatorIP)?.toDTO()?.let { paste.toPublicDTO(it) }) ?: return ResponseEntity.notFound().build() - return ResponseEntity.ok().body(json) - } - - @GetMapping("/get/{id}/content") - fun getContent( - @RequestParam(required = false, defaultValue = "true") compress: Boolean, - @PathVariable id: String, - request: HttpServletRequest - ): ResponseEntity { - if (!userService.processRequest(request)) { - return ResponseEntity.status(403).body("Prohibited") - } - - if (!bucket.tryConsume(1)) { - return ResponseEntity.status(429).body(null) - } - - val headers = HttpHeaders() - - if (compress) { - headers.add("Content-Encoding", "gzip") - } else { - headers.add("Content-Type", "text/plain; charset=utf-8") - } - - val paste = r2Service.getFile(id) ?: return ResponseEntity.notFound().build() - val pasteData = paste.toByteArray() - - return if (compress) { - val compressed = compressData(pasteData) - ResponseEntity.ok() - .contentType(MediaType.TEXT_PLAIN) - .headers(headers) - .body(compressed) - } else { - ResponseEntity.ok() - .contentType(MediaType.TEXT_PLAIN) - .headers(headers) - .body(paste) - } - } - - private fun compressData(data: ByteArray): ByteArray { - val byteArrayOutputStream = ByteArrayOutputStream() - val gzipOutputStream = GZIPOutputStream(byteArrayOutputStream) - - gzipOutputStream.use { outputStream -> - outputStream.write(data) - } - - return byteArrayOutputStream.toByteArray() - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/controllers/UploadController.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/controllers/UploadController.kt deleted file mode 100644 index b4ec7e5..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/controllers/UploadController.kt +++ /dev/null @@ -1,89 +0,0 @@ -package dev.loudbook.pastebook.controllers - -import dev.loudbook.pastebook.BucketUtils -import dev.loudbook.pastebook.ContentScanner -import dev.loudbook.pastebook.IPUtils -import dev.loudbook.pastebook.data.PastePrivateDTO -import dev.loudbook.pastebook.data.R2Service -import dev.loudbook.pastebook.mongo.PasteRepository -import dev.loudbook.pastebook.mongo.UserService -import io.github.bucket4j.Bucket -import jakarta.servlet.http.HttpServletRequest -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.HttpHeaders -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RestController - -@RestController -class UploadController { - @Autowired - lateinit var r2Service: R2Service - - @Autowired - lateinit var pasteRepository: PasteRepository - - @Autowired - lateinit var userService: UserService - - private val bucket: Bucket = BucketUtils.getBucketPerMinutes(4) - - @PostMapping(value = ["/upload"]) - fun upload(request: HttpServletRequest, @RequestBody body: String): ResponseEntity { - if (!userService.processRequest(request)) { - return ResponseEntity.status(403).body("Prohibited") - } - - if (!bucket.tryConsume(1)) { - return ResponseEntity.status(429).body("Rate limit exceeded") - } - - val header = HttpHeaders() - - val fileID = generateRandomString() - - val sinceTheEpoch = System.currentTimeMillis() - - val title = request.getHeader("title") ?: return ResponseEntity.badRequest().body("Title is required") - val reportBook = request.getHeader("reportBook")?.toBoolean() == true - val wrap = request.getHeader("wrap")?.toBoolean() == true - var expire = request.getHeader("expires")?.toLong() ?: (sinceTheEpoch + 8.64e+7).toLong() - - val hostDomain = request.getHeader("X-Domain-Name") - - if (expire < 60000) { - return ResponseEntity.badRequest().body("Expire time too short") - } - - if (expire < sinceTheEpoch) { - expire += sinceTheEpoch - } - - if (expire > (sinceTheEpoch + 2.765e+9)) { - return ResponseEntity.badRequest().body("Expire time too long") - } - - val filteredBody = ContentScanner.scanContent(body) - - val ip = IPUtils.getIPFromRequest(request) ?: return ResponseEntity.badRequest().body("Failed to get IP") - - val paste = PastePrivateDTO(fileID, title, sinceTheEpoch, reportBook, wrap, ip, expire) - - r2Service.uploadFile(fileID, filteredBody) - pasteRepository.save(paste) - - if (hostDomain != null) { - return ResponseEntity.ok().headers(header).body("https://$hostDomain/p/$fileID") - } - - return ResponseEntity.ok().headers(header).body(fileID) - } - - fun generateRandomString(length: Int = 5): String { - val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - return (1..length) - .map { chars.random() } - .joinToString("") - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/data/PasteDTO.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/data/PasteDTO.kt deleted file mode 100644 index 28d24e8..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/data/PasteDTO.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.loudbook.pastebook.data - -import org.springframework.data.annotation.Id - -@Suppress("unused") -class PasteDTO(@Id var id: String?, val user: UserDTO, val title: String, val created: Long, val reportBook: Boolean = false, val wrap: Boolean = false, val expiresAt: Long) \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/data/PastePrivateDTO.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/data/PastePrivateDTO.kt deleted file mode 100644 index 690dfcb..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/data/PastePrivateDTO.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.loudbook.pastebook.data - -import org.springframework.data.annotation.Id - -@Suppress("unused") -data class PastePrivateDTO(@Id var id: String?, val title: String, val created: Long, val reportBook: Boolean = false, val wrap: Boolean = false, val creatorIP: String, val expires: Long){ - fun toPublicDTO(user: UserDTO) = PasteDTO(id, user, title, created, reportBook, wrap, expires) -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/data/R2Service.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/data/R2Service.kt deleted file mode 100644 index 07a330f..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/data/R2Service.kt +++ /dev/null @@ -1,72 +0,0 @@ -package dev.loudbook.pastebook.data - -import jakarta.annotation.PostConstruct -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Service -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider -import software.amazon.awssdk.core.sync.RequestBody -import software.amazon.awssdk.regions.Region -import software.amazon.awssdk.services.s3.S3Client -import software.amazon.awssdk.services.s3.S3Configuration -import software.amazon.awssdk.services.s3.model.CreateBucketRequest -import software.amazon.awssdk.services.s3.model.GetObjectRequest -import software.amazon.awssdk.services.s3.model.ListObjectsRequest -import software.amazon.awssdk.services.s3.model.PutObjectRequest -import java.net.URI - -@Service -class R2Service { - private var amazonS3: S3Client? = null - - @Value("\${s3.accessKey}") - private val accessKey: String? = null - - @Value("\${s3.secretKey}") - private val secretKey: String? = null - - @Value("\${s3.url}") - private val url: String? = null - - @Value("\${s3.bucket}") - private val bucket: String? = null - - @PostConstruct - fun init() { - try { - amazonS3 = S3Client.builder() - .region(Region.US_EAST_1) - .credentialsProvider(AwsCredentialsProvider { AwsBasicCredentials.create(accessKey, secretKey) }) - .endpointOverride(URI.create(url!!)) - .serviceConfiguration(S3Configuration.builder() - .pathStyleAccessEnabled(true) - .build()) - .build() - - if (!amazonS3?.listBuckets()?.buckets()?.any { it.name() == bucket }!!) { - amazonS3?.createBucket(CreateBucketRequest.builder().bucket(bucket).build()) - } - } catch (e: Exception) { - e.printStackTrace() - } - } - - fun uploadFile(key: String, paste: String) { - amazonS3?.putObject(PutObjectRequest.builder().bucket(bucket).key(key).build(), RequestBody.fromString(paste)) - } - - fun deleteFile(key: String) { - amazonS3?.deleteObject { it.bucket(bucket).key(key) } - } - - fun getFile(key: String): String? { - val str = amazonS3?.getObject(GetObjectRequest.builder().bucket(bucket).key(key).build())?.readAllBytes() - ?.toString(Charsets.UTF_8) - - return str - } - - fun listFileNames(): List { - return amazonS3?.listObjects(ListObjectsRequest.builder().bucket(bucket).build())?.contents()?.map { it.key() } ?: emptyList() - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/data/User.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/data/User.kt deleted file mode 100644 index 1185c96..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/data/User.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.loudbook.pastebook.data - -import org.springframework.data.annotation.Id - -data class User(@Id val ip: String, val id: String, val requests: Int, val lastVisit: Long, val banned: Boolean) { - fun incrementRequests() = User(ip, id, requests + 1, System.currentTimeMillis(), banned) - fun toDTO() = UserDTO(id) -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/data/UserDTO.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/data/UserDTO.kt deleted file mode 100644 index a4bf6c3..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/data/UserDTO.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.loudbook.pastebook.data - -data class UserDTO(val id: String) \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/MigrationHandler.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/MigrationHandler.kt deleted file mode 100644 index fce5770..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/MigrationHandler.kt +++ /dev/null @@ -1,63 +0,0 @@ -package dev.loudbook.pastebook.mongo - -import org.bson.Document -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.boot.CommandLineRunner -import org.springframework.data.mongodb.core.MongoTemplate -import org.springframework.data.mongodb.core.query.Criteria -import org.springframework.data.mongodb.core.query.Query -import org.springframework.data.mongodb.core.query.Update -import org.springframework.stereotype.Component -import java.util.UUID - -@Component -class MigrationHandler(private val mongoTemplate: MongoTemplate) : CommandLineRunner { - - private val logger: Logger = LoggerFactory.getLogger(MigrationHandler::class.java) - - override fun run(vararg args: String?) { - idMigration(mongoTemplate) - } - - fun idMigration(mongoTemplate: MongoTemplate) { - val migrationQuery = Query(Criteria.where("migration").`is`("fixMissingIds")) - val existingMigration = mongoTemplate.findOne(migrationQuery, MigrationRecord::class.java) - - if (existingMigration != null) { - logger.info("ID migration already completed.") - return - } - - logger.info("Beginning migration of missing IDs...") - - val users = mongoTemplate.findAll(Document::class.java, "users") - - var result = 0 - - users.forEach { - val id = it.getString("id") - if (id == null) { - val newId = UUID.randomUUID().toString() - val query = Query(Criteria.where("_id").`is`(it.getObjectId("_id"))) - val update = Update().set("id", newId) - mongoTemplate.updateFirst(query, update, "users") - logger.info("Fixed missing ID for user ${it.getString("ip")}") - - result++ - } - } - - - if (result > 0) { - logger.info("Fixed ${result} missing IDs.") - } else { - logger.info("No missing IDs found.") - } - - mongoTemplate.save(MigrationRecord("fixMissingIds")) - logger.info("ID migration complete.") - } -} - -data class MigrationRecord(val migration: String) diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/PasteRepository.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/PasteRepository.kt deleted file mode 100644 index b6fadf1..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/PasteRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.loudbook.pastebook.mongo - -import dev.loudbook.pastebook.data.PastePrivateDTO -import org.springframework.data.mongodb.repository.MongoRepository -import org.springframework.data.mongodb.repository.Query - -interface PasteRepository : MongoRepository { - @Query(value = "{}", fields = "{ 'content' : 0 }") - fun findAllDTO(): List - - @Query("{ 'id' : ?0 }", fields = "{ 'content' : 0 }") - fun findDTOByID(id: String): PastePrivateDTO? -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UserService.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UserService.kt deleted file mode 100644 index b13a00f..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UserService.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.loudbook.pastebook.mongo - -import dev.loudbook.pastebook.IPUtils -import dev.loudbook.pastebook.data.User -import jakarta.servlet.http.HttpServletRequest -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Service -import java.util.UUID - -@Service -class UserService { - @Autowired - private lateinit var usersRepository: UsersRepository - - fun incrementRequests(ip: String) { - val user = usersRepository.findByIp(ip) - if (user != null) { - val newUser = user.incrementRequests() - usersRepository.save(newUser) - } else { - val userID = UUID.randomUUID().toString() - usersRepository.save(User(ip, userID, 1, System.currentTimeMillis(), false)) - } - } - - fun processRequest(request: HttpServletRequest): Boolean { - val ip = IPUtils.getIPFromRequest(request) ?: return false - val user = usersRepository.findByIp(ip) - if (user != null && user.banned) { - return false - } - - incrementRequests(ip) - return true - } - - fun getUser(ip: String): User? { - return usersRepository.findByIp(ip) - } -} \ No newline at end of file diff --git a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UsersRepository.kt b/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UsersRepository.kt deleted file mode 100644 index 36fc1cd..0000000 --- a/backend/src/main/kotlin/dev/loudbook/pastebook/mongo/UsersRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.loudbook.pastebook.mongo - -import dev.loudbook.pastebook.data.User -import org.springframework.data.mongodb.repository.MongoRepository -import org.springframework.data.mongodb.repository.Query - -interface UsersRepository : MongoRepository { - @Query("{ 'ip' : ?0 }") - fun findByIp(ip: String): User? -} \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml deleted file mode 100644 index 95edac5..0000000 --- a/backend/src/main/resources/application.yml +++ /dev/null @@ -1,15 +0,0 @@ -server: - port: 8080 - -spring: - application: - name: PasteBook - data: - mongodb: - uri: ${SPRING_DATA_MONGODB_URI} - -s3: - url: ${S3_ENDPOINT} - accessKey: ${S3_ACCESS_KEY_ID} - secretKey: ${S3_SECRET_ACCESS_KEY} - bucket: ${S3_BUCKET} \ No newline at end of file diff --git a/backend/src/models/mod.rs b/backend/src/models/mod.rs new file mode 100644 index 0000000..5d536bd --- /dev/null +++ b/backend/src/models/mod.rs @@ -0,0 +1,2 @@ +pub mod user; +pub mod paste; \ No newline at end of file diff --git a/backend/src/models/paste.rs b/backend/src/models/paste.rs new file mode 100644 index 0000000..dae7a76 --- /dev/null +++ b/backend/src/models/paste.rs @@ -0,0 +1,38 @@ +use serde::{Deserialize, Serialize}; +use crate::models::user::UserDTO; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Paste { + pub id: Option, + pub title: String, + pub created: u64, + pub report_book: bool, + pub wrap: bool, + pub creator_ip: String, + pub expires: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PasteDTO { + pub id: Option, + pub user: UserDTO, + pub title: String, + pub created: u64, + pub report_book: bool, + pub wrap: bool, + pub expires: u64, +} + +impl Paste { + pub fn to_public_dto(&self, user: UserDTO) -> PasteDTO { + PasteDTO { + id: self.id.clone(), + user, + title: self.title.clone(), + created: self.created, + report_book: self.report_book, + wrap: self.wrap, + expires: self.expires, + } + } +} diff --git a/backend/src/models/user.rs b/backend/src/models/user.rs new file mode 100644 index 0000000..66580a6 --- /dev/null +++ b/backend/src/models/user.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct User { + pub ip: String, + pub id: String, + pub requests: u64, + pub created_at: i64, + pub banned: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UserDTO { + pub id: String, + pub requests: u64, + pub created_at: i64, + pub banned: bool, +} + +impl User { + pub fn to_dto(&self) -> UserDTO { + UserDTO { + id: self.id.clone(), + requests: self.requests, + created_at: self.created_at, + banned: self.banned, + } + } +} \ No newline at end of file diff --git a/backend/src/mongodb_service.rs b/backend/src/mongodb_service.rs new file mode 100644 index 0000000..50f4f2f --- /dev/null +++ b/backend/src/mongodb_service.rs @@ -0,0 +1,80 @@ +use mongodb::bson::{doc, uuid}; +use mongodb::{Client, Collection, Cursor}; +use mongodb::options::ClientOptions; +use crate::models::paste::Paste; +use crate::models::user::User; +use crate::mongoresult::MongoResult; + +pub struct MongoService { + client: Client, + user_collection: Collection, + paste_collection: Collection, +} + +impl MongoService { + pub async fn new(uri: &str, db_name: &str) -> MongoResult { + let client_options = ClientOptions::parse(uri).await?; + let client = Client::with_options(client_options)?; + + let database = client.database(db_name); + let user_collection = database.collection::("users"); + let paste_collection = database.collection::("pastes"); + + Ok(Self { + client, + user_collection, + paste_collection, + }) + } + + pub async fn increment_requests(&self, ip: &str) -> MongoResult<()> { + let filter = doc! { "ip": ip }; + let update = doc! { + "$inc": { "request_count": 1 }, + "$setOnInsert": { + "user_id": uuid::Uuid::new().to_string(), + "created_at": chrono::Utc::now().timestamp_millis(), + "banned": false + } + }; + + self.user_collection.update_one(filter, update).upsert(true).await?; + Ok(()) + } + + pub async fn put_paste(&self, paste: Paste) -> MongoResult<()> { + self.paste_collection.insert_one(paste).await?; + Ok(()) + } + + pub async fn delete_paste(&self, id: &str) -> MongoResult<()> { + self.paste_collection.delete_one(doc! { "id": id }).await?; + Ok(()) + } + + pub async fn is_user_banned(&self, ip: &str) -> MongoResult { + if let Some(user) = self.user_collection.find_one(doc! { "ip": ip }).await? { + Ok(user.banned) + } else { + Ok(false) + } + } + + pub async fn get_user(&self, ip: &str) -> MongoResult> { + let user = self.user_collection.find_one(doc! { "ip": ip }).await?; + Ok(user) + } + + pub async fn get_paste_metadata(&self, id: &str) -> MongoResult> { + let paste = self.paste_collection.find_one(doc! { "id": id }).await?; + Ok(paste) + } + + pub async fn get_all_pastes_metadata(&self) -> MongoResult> { + let cursor = self + .paste_collection + .find(doc! {}) + .await?; + Ok(cursor) + } +} \ No newline at end of file diff --git a/backend/src/mongoresult.rs b/backend/src/mongoresult.rs new file mode 100644 index 0000000..434e7c6 --- /dev/null +++ b/backend/src/mongoresult.rs @@ -0,0 +1,3 @@ +use mongodb::error::Error; + +pub type MongoResult = Result; \ No newline at end of file diff --git a/backend/src/test/kotlin/dev/loudbook/pastebook/pastebook/PasteBookApplicationTests.kt b/backend/src/test/kotlin/dev/loudbook/pastebook/pastebook/PasteBookApplicationTests.kt deleted file mode 100644 index d421c14..0000000 --- a/backend/src/test/kotlin/dev/loudbook/pastebook/pastebook/PasteBookApplicationTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.loudbook.pastebook.pastebook - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class PasteBookApplicationTests { - - @Test - fun contextLoads() { - } - -} diff --git a/backend/src/utils/iputils.rs b/backend/src/utils/iputils.rs new file mode 100644 index 0000000..017265f --- /dev/null +++ b/backend/src/utils/iputils.rs @@ -0,0 +1,22 @@ +use actix_web::HttpRequest; + +pub struct IPUtils; + +impl IPUtils { + pub fn get_ip_from_request(req: &HttpRequest) -> Option { + if let Some(ip) = req.headers().get("Cf-Connecting-IP").and_then(|v| v.to_str().ok()) { + return Some(Self::extract_first_ip(ip)); + } + + if let Some(ip) = req.headers().get("CF-Connecting-IPv6").and_then(|v| v.to_str().ok()) { + return Some(Self::extract_first_ip(ip)); + } + + req.peer_addr() + .map(|addr| addr.ip().to_string()) + } + + fn extract_first_ip(ip_list: &str) -> String { + ip_list.split(',').next().unwrap_or("").trim().to_string() + } +} diff --git a/backend/src/utils/mod.rs b/backend/src/utils/mod.rs new file mode 100644 index 0000000..e38bd47 --- /dev/null +++ b/backend/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod iputils; \ No newline at end of file From e229516d8b4d94e358c2144c8b361966abea43df Mon Sep 17 00:00:00 2001 From: Loudbook Date: Mon, 9 Dec 2024 20:22:40 -0500 Subject: [PATCH 02/43] Cargo --- backend/Cargo.lock | 3233 ++++++++++++++++++++++++++++++++++++++++++++ backend/Cargo.toml | 19 + 2 files changed, 3252 insertions(+) create mode 100644 backend/Cargo.lock create mode 100644 backend/Cargo.toml diff --git a/backend/Cargo.lock b/backend/Cargo.lock new file mode 100644 index 0000000..862ef2b --- /dev/null +++ b/backend/Cargo.lock @@ -0,0 +1,3233 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.22.1", + "bitflags 2.6.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.90", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ba2c5c0f2618937ce3d4a5ad574b86775576fa24006bcb3128c6e2cbf3c34e" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.2.0", + "once_cell", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper", + "hyper-rustls", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.2.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.2.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "backend" +version = "0.1.0" +dependencies = [ + "actix-web", + "anyhow", + "aws-credential-types", + "aws-sdk-s3", + "chrono", + "dotenv", + "flate2", + "futures-util", + "log", + "mongodb", + "rand", + "serde", + "tokio", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bson" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59" +dependencies = [ + "ahash", + "base64 0.13.1", + "bitvec", + "hex", + "indexmap 2.7.0", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.90", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.90", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.4.0", + "ipnet", + "once_cell", + "rand", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae21c3177a27788957044151cc2800043d127acaa460a47ebb9b84dfa2c6aa0" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "libc" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "mongodb" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c857d71f918b38221baf2fdff7207fec9984b4504901544772b1edf0302d669f" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bitflags 1.3.2", + "bson", + "chrono", + "derivative", + "derive_more", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hickory-proto", + "hickory-resolver", + "hmac", + "md-5", + "mongodb-internal-macros", + "once_cell", + "pbkdf2", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls", + "rustls-pemfile", + "serde", + "serde_bytes", + "serde_with", + "sha-1", + "sha2", + "socket2", + "stringprep", + "strsim", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "typed-builder", + "uuid", + "webpki-roots", +] + +[[package]] +name = "mongodb-internal-macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6dbc533e93429a71c44a14c04547ac783b56d3f22e6c4f12b1b994cf93844e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "indexmap 2.7.0", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna 1.0.3", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.90", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/backend/Cargo.toml b/backend/Cargo.toml new file mode 100644 index 0000000..68a28d5 --- /dev/null +++ b/backend/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "backend" +version = "0.1.0" +edition = "2024" + +[dependencies] +actix-web = "4" +mongodb = "3.1.0" +aws-sdk-s3 = { version = "1.65.0" } +tokio = { version = "1", features = ["full"] } +anyhow = "1.0.94" +serde = { version = "1.0.215", features = ["derive"] } +flate2 = "1.0.35" +chrono = "0.4.39" +rand = "0.8.5" +log = "0.4.22" +dotenv = "0.15" +futures-util = "0.3.31" +aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } \ No newline at end of file From 758807a4915adb9cdbe4d1640872775a6c3efc81 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 14:32:57 -0500 Subject: [PATCH 03/43] Formatting --- backend/Cargo.lock | 97 ++++++++++++++++++++++++++++++++++ backend/Cargo.toml | 5 +- backend/Dockerfile | 10 ++++ backend/src/aws_service.rs | 22 ++++++-- backend/src/main.rs | 72 +++++++++++++++++++++++++ backend/src/mongodb_service.rs | 5 +- 6 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 backend/src/main.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 862ef2b..a02456d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -258,6 +258,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.94" @@ -558,6 +607,7 @@ dependencies = [ "aws-sdk-s3", "chrono", "dotenv", + "env_logger", "flate2", "futures-util", "log", @@ -766,6 +816,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "const-oid" version = "0.9.6" @@ -1036,6 +1092,29 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1385,6 +1464,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.31" @@ -1649,6 +1734,12 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.14" @@ -2791,6 +2882,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.11.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 68a28d5..2aeaffd 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "backend" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] actix-web = "4" @@ -16,4 +16,5 @@ rand = "0.8.5" log = "0.4.22" dotenv = "0.15" futures-util = "0.3.31" -aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } \ No newline at end of file +aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } +env_logger = "0.11.5" \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..ddeb490 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,10 @@ +FROM rust:latest + +COPY ./ ./ + +RUN cargo build + +RUN cp ./target/debug/backend ./ +RUN rm -rf ./src ./Cargo.toml ./Cargo.lock ./target + +CMD ["./backend"] diff --git a/backend/src/aws_service.rs b/backend/src/aws_service.rs index 50c02d9..2126f9b 100644 --- a/backend/src/aws_service.rs +++ b/backend/src/aws_service.rs @@ -10,18 +10,23 @@ pub struct AWSService { impl AWSService { pub async fn new(endpoint: &str, bucket_name: &str, access_key: &str, secret_key: &str) -> Result { + println!("Connecting to AWS S3..."); let region = Region::new("auto"); let credentials = Credentials::from_keys(access_key, secret_key, None); let shared_credentials = SharedCredentialsProvider::new(credentials); let config = Config::builder() + .force_path_style(true) .region(region) .credentials_provider(shared_credentials) .endpoint_url(endpoint) + .behavior_version_latest() .build(); let client = Client::from_conf(config); + println!("Connected to AWS S3"); + Ok(Self { client, bucket_name: bucket_name.to_string(), @@ -29,7 +34,7 @@ impl AWSService { } pub async fn get_file(&self, key: &str) -> Result> { - let resp = self + let response = self .client .get_object() .bucket(&self.bucket_name) @@ -37,7 +42,7 @@ impl AWSService { .send() .await?; - let data = resp.body.collect().await?; + let data = response.body.collect().await?; Ok(data.into_bytes().to_vec()) } @@ -63,7 +68,7 @@ impl AWSService { } pub async fn list_files(&self) -> Result> { - let resp = self + let response = self .client .list_objects_v2() .bucket(&self.bucket_name) @@ -71,7 +76,7 @@ impl AWSService { .await?; let mut keys = Vec::new(); - for object in resp.contents.unwrap_or_default() { + for object in response.contents.unwrap_or_default() { if let Some(key) = object.key { keys.push(key); } @@ -79,4 +84,13 @@ impl AWSService { Ok(keys) } + + pub async fn create_bucket(&self, bucket_name: &str) -> Result<()> { + self.client + .create_bucket() + .bucket(bucket_name) + .send() + .await?; + Ok(()) + } } diff --git a/backend/src/main.rs b/backend/src/main.rs new file mode 100644 index 0000000..084088a --- /dev/null +++ b/backend/src/main.rs @@ -0,0 +1,72 @@ +mod aws_service; +mod mongodb_service; +mod models; +mod controllers; +mod mongoresult; +mod utils; +mod delete_service; + +use crate::aws_service::AWSService; +use crate::controllers::get_controller::{get_content_handler, get_metadata_handler}; +use crate::controllers::upload_controller::upload_handler; +use crate::delete_service::DeleteHandler; +use crate::mongodb_service::MongoService; +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use dotenv::dotenv; +use std::env; +use std::sync::Arc; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + env::set_var("RUST_LOG", "debug"); + env_logger::init(); + dotenv().ok(); + + let database_url = env::var("S3_ENDPOINT").expect("S3_ENDPOINT must be set"); + let aws_access_key = env::var("S3_ACCESS_KEY_ID").expect("S3_ACCESS_KEY_ID must be set"); + let aws_secret_key = env::var("S3_SECRET_ACCESS_KEY").expect("S3_SECRET_ACCESS_KEY must be set"); + let bucket_name = env::var("S3_BUCKET").expect("S3_BUCKET must be set"); + let mongo_url = env::var("SPRING_DATA_MONGODB_URI").expect("SPRING_DATA_MONGODB_URI must be set"); + + let aws_service = Arc::new( + AWSService::new( + &database_url, + &bucket_name, + &aws_access_key, + &aws_secret_key + ) + .await + .expect("Failed to initialize AWSService"), + ); + + aws_service.create_bucket(&bucket_name).await.expect("Failed to create bucket"); + + let mongo_service = Arc::new( + MongoService::new( + &mongo_url, + "pastebook" + ) + .await + .expect("Failed to initialize MongoService") + ); + + let delete_handler = DeleteHandler::new(Arc::clone(&aws_service), Arc::clone(&mongo_service)); + delete_handler.start_delete_loop(); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(Arc::clone(&aws_service))) + .app_data(web::Data::new(Arc::clone(&mongo_service))) + .route("/get/{id}/content", web::get().to(get_content_handler)) + .route("/get/{id}/metadata", web::get().to(get_metadata_handler)) + .route("/upload", web::post().to(upload_handler)) + }) + .bind(("0.0.0.0", 8080))? + .run() + .await +} + +async fn health_check() -> impl Responder { + println!("Health check"); + HttpResponse::Ok().body("OK") +} \ No newline at end of file diff --git a/backend/src/mongodb_service.rs b/backend/src/mongodb_service.rs index 50f4f2f..2d44378 100644 --- a/backend/src/mongodb_service.rs +++ b/backend/src/mongodb_service.rs @@ -6,13 +6,13 @@ use crate::models::user::User; use crate::mongoresult::MongoResult; pub struct MongoService { - client: Client, user_collection: Collection, paste_collection: Collection, } impl MongoService { pub async fn new(uri: &str, db_name: &str) -> MongoResult { + println!("Connecting to MongoDB..."); let client_options = ClientOptions::parse(uri).await?; let client = Client::with_options(client_options)?; @@ -20,8 +20,9 @@ impl MongoService { let user_collection = database.collection::("users"); let paste_collection = database.collection::("pastes"); + println!("Connected to MongoDB"); + Ok(Self { - client, user_collection, paste_collection, }) From faadc29e031823d97db6e57315f33caecc77d2eb Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 16:46:03 -0500 Subject: [PATCH 04/43] Fixes --- backend/Dockerfile | 13 +++++++------ backend/src/aws_service.rs | 4 ++++ backend/src/controllers/upload_controller.rs | 4 +++- backend/src/delete_service.rs | 8 +++----- backend/src/main.rs | 1 - backend/src/models/paste.rs | 4 ++-- backend/src/mongodb_service.rs | 4 ++-- 7 files changed, 21 insertions(+), 17 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index ddeb490..06750c7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,10 +1,11 @@ -FROM rust:latest - -COPY ./ ./ +FROM rust:latest as builder +COPY ./Cargo.toml ./Cargo.lock ./ +COPY ./src ./src RUN cargo build -RUN cp ./target/debug/backend ./ -RUN rm -rf ./src ./Cargo.toml ./Cargo.lock ./target +FROM rust:latest + +COPY --from=builder target/debug/backend /usr/local/bin/backend -CMD ["./backend"] +CMD ["/usr/local/bin/backend"] \ No newline at end of file diff --git a/backend/src/aws_service.rs b/backend/src/aws_service.rs index 2126f9b..127c9ba 100644 --- a/backend/src/aws_service.rs +++ b/backend/src/aws_service.rs @@ -86,6 +86,10 @@ impl AWSService { } pub async fn create_bucket(&self, bucket_name: &str) -> Result<()> { + if self.client.head_bucket().bucket(bucket_name).send().await.is_ok() { + return Ok(()); + } + self.client .create_bucket() .bucket(bucket_name) diff --git a/backend/src/controllers/upload_controller.rs b/backend/src/controllers/upload_controller.rs index 1edc330..61ed386 100644 --- a/backend/src/controllers/upload_controller.rs +++ b/backend/src/controllers/upload_controller.rs @@ -63,7 +63,7 @@ pub async fn upload_handler( let file_id = generate_random_string(5); let paste = Paste { - id: Some(file_id.clone()), + id: file_id.clone(), title: title.to_string(), created: since_the_epoch, report_book, @@ -79,6 +79,8 @@ pub async fn upload_handler( return HttpResponse::InternalServerError().body(format!("Failed to save to database: {:?}", e)); } + mongo_service.increment_requests(&ip).await.expect("Failed to increment requests"); + let host_domain = req .headers() .get("X-Domain-Name") diff --git a/backend/src/delete_service.rs b/backend/src/delete_service.rs index c101f21..c314b85 100644 --- a/backend/src/delete_service.rs +++ b/backend/src/delete_service.rs @@ -60,22 +60,20 @@ impl DeleteHandler { } for paste in &deletable_pastes { - let paste_id = paste.id.as_deref().unwrap_or_default(); + let paste_id = &paste.id; if let Err(err) = mongo_service.delete_paste(paste_id).await { error!("Failed to delete paste from database: {:?}", err); } - if let Some(id) = &paste.id { + if let id = &paste.id { if let Err(err) = mongo_service.delete_paste(id).await { error!("Failed to delete paste file: {}", err); } - } else { - error!("Paste ID is missing for: {:?}", paste); } } if let Ok(file_names) = aws_service.list_files().await { for file_name in file_names { - if all_pastes.iter().all(|paste| paste.id.as_deref() != Some(&file_name)) { + if all_pastes.iter().all(|paste| &paste.id != &file_name) { if let Err(err) = aws_service.delete_file(&file_name).await { error!("Failed to delete invalid file {}: {}", file_name, err); } else { diff --git a/backend/src/main.rs b/backend/src/main.rs index 084088a..cf77410 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -18,7 +18,6 @@ use std::sync::Arc; #[actix_web::main] async fn main() -> std::io::Result<()> { - env::set_var("RUST_LOG", "debug"); env_logger::init(); dotenv().ok(); diff --git a/backend/src/models/paste.rs b/backend/src/models/paste.rs index dae7a76..2786c4d 100644 --- a/backend/src/models/paste.rs +++ b/backend/src/models/paste.rs @@ -3,7 +3,7 @@ use crate::models::user::UserDTO; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Paste { - pub id: Option, + pub id: String, pub title: String, pub created: u64, pub report_book: bool, @@ -14,7 +14,7 @@ pub struct Paste { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PasteDTO { - pub id: Option, + pub id: String, pub user: UserDTO, pub title: String, pub created: u64, diff --git a/backend/src/mongodb_service.rs b/backend/src/mongodb_service.rs index 2d44378..db2e1cc 100644 --- a/backend/src/mongodb_service.rs +++ b/backend/src/mongodb_service.rs @@ -31,9 +31,9 @@ impl MongoService { pub async fn increment_requests(&self, ip: &str) -> MongoResult<()> { let filter = doc! { "ip": ip }; let update = doc! { - "$inc": { "request_count": 1 }, + "$inc": { "requests": 1 }, "$setOnInsert": { - "user_id": uuid::Uuid::new().to_string(), + "id": uuid::Uuid::new().to_string(), "created_at": chrono::Utc::now().timestamp_millis(), "banned": false } From 8292dc81569c82a194b8197ef1f37cc50ec385cd Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 16:53:24 -0500 Subject: [PATCH 05/43] Update docker-compose.yml --- docker-compose.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 24b1aa6..1139491 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,8 +11,8 @@ services: - S3_BUCKET=pastebook - S3_ENDPOINT=http://minio:9000 depends_on: - - mongo - - minio + minio: + condition: service_healthy networks: - pastebook-network @@ -43,7 +43,7 @@ services: networks: - pastebook-network pull_policy: always - + minio: image: quay.io/minio/minio:latest container_name: minio @@ -60,7 +60,12 @@ services: aliases: - pastebook.minio pull_policy: always - + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 2s + timeout: 2s + retries: 5 + volumes: mongo-data: minio-data: From 0eadec5f1769b442a3ae34b5f2cb0ad47ed22911 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 16:55:21 -0500 Subject: [PATCH 06/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 58d0850..97d3596 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -7,6 +7,7 @@ on: - backend/** branches: - master + - dev jobs: build: @@ -45,6 +46,19 @@ jobs: id: get_commit_hash run: echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + - name: Set Docker Tags + id: set_tags + run: | + if [ "${{ github.ref_name }}" == "master" ]; then + echo "tag=latest" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "dev" ]; then + echo "tag=dev" >> $GITHUB_ENV + else + echo "Unknown branch: ${{ github.ref_name }}" + exit 1 + fi + echo "branch_tag=${{ env.tag }}" >> $GITHUB_ENV + - name: Build and push frontend Docker image to GHCR uses: docker/build-push-action@v3 with: @@ -54,7 +68,7 @@ jobs: COMMIT_HASH=${{ env.hash }} push: true tags: | - ghcr.io/loudbooks/pastebook-frontend:latest + ghcr.io/loudbooks/pastebook-frontend:${{ env.branch_tag }} ghcr.io/loudbooks/pastebook-frontend:${{ env.hash }} - name: Build and push backend Docker image to GHCR @@ -64,7 +78,7 @@ jobs: file: ./backend/Dockerfile push: true tags: | - ghcr.io/loudbooks/pastebook-backend:latest + ghcr.io/loudbooks/pastebook-backend:${{ env.branch_tag }} ghcr.io/loudbooks/pastebook-backend:${{ env.hash }} - name: Log in to Docker Hub @@ -82,7 +96,7 @@ jobs: file: ./frontend/Dockerfile push: true tags: | - ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:latest + ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.branch_tag }} ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.hash }} - name: Build and push backend Docker image to Docker Hub @@ -92,5 +106,5 @@ jobs: file: ./backend/Dockerfile push: true tags: | - ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:latest + ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.branch_tag }} ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.hash }} From 8433a3e3e2940d11260e9936364ccb4c3e8bae6b Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 16:56:14 -0500 Subject: [PATCH 07/43] CORS --- backend/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/src/main.rs b/backend/src/main.rs index cf77410..f51f9f8 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -15,6 +15,7 @@ use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use dotenv::dotenv; use std::env; use std::sync::Arc; +use aws_sdk_s3::types::builders::CorsRuleBuilder; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -52,8 +53,14 @@ async fn main() -> std::io::Result<()> { let delete_handler = DeleteHandler::new(Arc::clone(&aws_service), Arc::clone(&mongo_service)); delete_handler.start_delete_loop(); + let cors = CorsRuleBuilder::default() + .allowed_origins(vec!["*"]) + .allowed_methods(vec!["GET", "POST"]) + .allowed_headers(vec!["*"]) + .build(); + HttpServer::new(move || { - App::new() + App::new().wrap(cors) .app_data(web::Data::new(Arc::clone(&aws_service))) .app_data(web::Data::new(Arc::clone(&mongo_service))) .route("/get/{id}/content", web::get().to(get_content_handler)) From 41d4e0651f6a9b8bbd997db1933ddaddaa896286 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 16:59:08 -0500 Subject: [PATCH 08/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 97d3596..971daa2 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -18,19 +18,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 - - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 23 - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - - - name: Build with Gradle (backend) - run: | - cd backend - ./gradlew bootJar - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 From a66a8f0e4ebda969a4c1bd4805ed5110142d7d37 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:01:37 -0500 Subject: [PATCH 09/43] Use bucket name for database --- backend/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main.rs b/backend/src/main.rs index f51f9f8..b307ac3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -44,7 +44,7 @@ async fn main() -> std::io::Result<()> { let mongo_service = Arc::new( MongoService::new( &mongo_url, - "pastebook" + &bucket_name, ) .await .expect("Failed to initialize MongoService") From 8d8ac712b48887f9f9fddf04886ee3c020a236d5 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:10:01 -0500 Subject: [PATCH 10/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 971daa2..95f415d 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,9 +2,9 @@ name: Build and Publish Docker Images on: push: - paths: - - frontend/** - - backend/** + # paths: + # - frontend/** + # - backend/** branches: - master - dev @@ -45,7 +45,13 @@ jobs: exit 1 fi echo "branch_tag=${{ env.tag }}" >> $GITHUB_ENV - + + - name: Debug Branch and Tags + run: | + echo "Branch name: ${{ github.ref_name }}" + echo "Tag: ${{ env.tag }}" + echo "Branch tag: ${{ env.branch_tag }}" + - name: Build and push frontend Docker image to GHCR uses: docker/build-push-action@v3 with: From 5c2973844311656178815b039702f02ad1505bca Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:11:15 -0500 Subject: [PATCH 11/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 95f415d..581a98c 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -44,13 +44,6 @@ jobs: echo "Unknown branch: ${{ github.ref_name }}" exit 1 fi - echo "branch_tag=${{ env.tag }}" >> $GITHUB_ENV - - - name: Debug Branch and Tags - run: | - echo "Branch name: ${{ github.ref_name }}" - echo "Tag: ${{ env.tag }}" - echo "Branch tag: ${{ env.branch_tag }}" - name: Build and push frontend Docker image to GHCR uses: docker/build-push-action@v3 @@ -61,7 +54,7 @@ jobs: COMMIT_HASH=${{ env.hash }} push: true tags: | - ghcr.io/loudbooks/pastebook-frontend:${{ env.branch_tag }} + ghcr.io/loudbooks/pastebook-frontend:${{ env.tag }} ghcr.io/loudbooks/pastebook-frontend:${{ env.hash }} - name: Build and push backend Docker image to GHCR @@ -71,7 +64,7 @@ jobs: file: ./backend/Dockerfile push: true tags: | - ghcr.io/loudbooks/pastebook-backend:${{ env.branch_tag }} + ghcr.io/loudbooks/pastebook-backend:${{ env.tag }} ghcr.io/loudbooks/pastebook-backend:${{ env.hash }} - name: Log in to Docker Hub @@ -89,7 +82,7 @@ jobs: file: ./frontend/Dockerfile push: true tags: | - ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.branch_tag }} + ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.tag }} ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.hash }} - name: Build and push backend Docker image to Docker Hub @@ -99,5 +92,5 @@ jobs: file: ./backend/Dockerfile push: true tags: | - ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.branch_tag }} + ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.tag }} ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.hash }} From e5deff9b14dde6b861d834a01b3b16b0fc7fa927 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:13:43 -0500 Subject: [PATCH 12/43] Optimize Dockerfile --- backend/Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 06750c7..4e0300a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,11 +1,12 @@ FROM rust:latest as builder COPY ./Cargo.toml ./Cargo.lock ./ +RUN cargo fetch COPY ./src ./src -RUN cargo build +RUN cargo build --release -FROM rust:latest +FROM debian:bullseye-slim -COPY --from=builder target/debug/backend /usr/local/bin/backend +COPY --from=builder target/release/backend /usr/local/bin/backend CMD ["/usr/local/bin/backend"] \ No newline at end of file From d2d6183106dc49ba3e7b4b90cf615588a44bb0e8 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:16:08 -0500 Subject: [PATCH 13/43] Target --- backend/Cargo.toml | 6 +++++- backend/Dockerfile | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2aeaffd..d99665b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -17,4 +17,8 @@ log = "0.4.22" dotenv = "0.15" futures-util = "0.3.31" aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } -env_logger = "0.11.5" \ No newline at end of file +env_logger = "0.11.5" + +[[bin]] +name = "backend" +path = "src/main.rs" \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 4e0300a..61f057b 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:latest as builder +FROM rust:latest AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch From 4792ebd93386b48bfbecaa7fe23b315d0429b2f0 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:26:13 -0500 Subject: [PATCH 14/43] Cors fix --- backend/Cargo.lock | 16 ++++++++++++++++ backend/Cargo.toml | 1 + backend/src/main.rs | 15 ++++++--------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index a02456d..13c9fb2 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.9.0" @@ -601,6 +616,7 @@ dependencies = [ name = "backend" version = "0.1.0" dependencies = [ + "actix-cors", "actix-web", "anyhow", "aws-credential-types", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d99665b..f60dc62 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] actix-web = "4" +actix-cors = "0.7.0" mongodb = "3.1.0" aws-sdk-s3 = { version = "1.65.0" } tokio = { version = "1", features = ["full"] } diff --git a/backend/src/main.rs b/backend/src/main.rs index b307ac3..569b1f3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -15,7 +15,7 @@ use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use dotenv::dotenv; use std::env; use std::sync::Arc; -use aws_sdk_s3::types::builders::CorsRuleBuilder; +use actix_cors::Cors; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -52,15 +52,12 @@ async fn main() -> std::io::Result<()> { let delete_handler = DeleteHandler::new(Arc::clone(&aws_service), Arc::clone(&mongo_service)); delete_handler.start_delete_loop(); - - let cors = CorsRuleBuilder::default() - .allowed_origins(vec!["*"]) - .allowed_methods(vec!["GET", "POST"]) - .allowed_headers(vec!["*"]) - .build(); - + HttpServer::new(move || { - App::new().wrap(cors) + let cors = Cors::permissive(); + + App::new() + .wrap(cors) .app_data(web::Data::new(Arc::clone(&aws_service))) .app_data(web::Data::new(Arc::clone(&mongo_service))) .route("/get/{id}/content", web::get().to(get_content_handler)) From c1019daf71610c51b90d9db44804a82da48f31bf Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:37:30 -0500 Subject: [PATCH 15/43] Ubuntu --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 61f057b..bbfd04e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,7 +5,7 @@ RUN cargo fetch COPY ./src ./src RUN cargo build --release -FROM debian:bullseye-slim +FROM ubuntu/latest COPY --from=builder target/release/backend /usr/local/bin/backend From 8682144c5f3e47d1d678b7602b46e384a344fe91 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:40:42 -0500 Subject: [PATCH 16/43] Ubuntu Noble --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index bbfd04e..ce88737 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,7 +5,7 @@ RUN cargo fetch COPY ./src ./src RUN cargo build --release -FROM ubuntu/latest +FROM ubuntu:noble COPY --from=builder target/release/backend /usr/local/bin/backend From 84c67a6149040b6845e68711e44d74db176fedee Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 17:49:31 -0500 Subject: [PATCH 17/43] Alpine --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index ce88737..a4de00a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,9 +3,9 @@ FROM rust:latest AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch COPY ./src ./src -RUN cargo build --release +RUN cargo build --release --target x86_64-unknown-linux-musl -FROM ubuntu:noble +FROM alpine:latest COPY --from=builder target/release/backend /usr/local/bin/backend From ac33784e12303266b6b089c4b70c33bc5b3e5d5e Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:01:46 -0500 Subject: [PATCH 18/43] 24.04 --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index a4de00a..9c728a4 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,9 +3,9 @@ FROM rust:latest AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch COPY ./src ./src -RUN cargo build --release --target x86_64-unknown-linux-musl +RUN cargo build --release -FROM alpine:latest +FROM ubuntu:24.04 COPY --from=builder target/release/backend /usr/local/bin/backend From d9d25075156dd4826facdf9f32435c83249e88e3 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:11:04 -0500 Subject: [PATCH 19/43] Bullseye --- backend/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 9c728a4..2866901 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,8 +5,9 @@ RUN cargo fetch COPY ./src ./src RUN cargo build --release -FROM ubuntu:24.04 +FROM debian:bullseye +RUN apt-get update && apt-get install -y libc6 COPY --from=builder target/release/backend /usr/local/bin/backend CMD ["/usr/local/bin/backend"] \ No newline at end of file From a4e24924991fb20adb1c2c99416c5a3fe180eaf2 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:18:12 -0500 Subject: [PATCH 20/43] Bookwork --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 2866901..e864549 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,7 +5,7 @@ RUN cargo fetch COPY ./src ./src RUN cargo build --release -FROM debian:bullseye +FROM debian:bookworm RUN apt-get update && apt-get install -y libc6 COPY --from=builder target/release/backend /usr/local/bin/backend From 1b092460d31e9a8afc3461cbb0e45702168d7753 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:30:33 -0500 Subject: [PATCH 21/43] Bookworm --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index e864549..bd5d3bc 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,11 +1,11 @@ -FROM rust:latest AS builder +FROM rust:slim-bookworm AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch COPY ./src ./src RUN cargo build --release -FROM debian:bookworm +FROM debian:bookworm-slim RUN apt-get update && apt-get install -y libc6 COPY --from=builder target/release/backend /usr/local/bin/backend From 9dede694809f95ca12bea844638ab3b121385887 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:34:51 -0500 Subject: [PATCH 22/43] Target musl --- backend/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index bd5d3bc..4cfdbd0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,7 +3,9 @@ FROM rust:slim-bookworm AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch COPY ./src ./src -RUN cargo build --release + +RUN rustup target add x86_64-unknown-linux-musl +RUN cargo build --release --target x86_64-unknown-linux-musl FROM debian:bookworm-slim From 53958810e90fb794ca33800184cacc301d25f4fd Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:37:52 -0500 Subject: [PATCH 23/43] MUSL Tools --- backend/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/Dockerfile b/backend/Dockerfile index 4cfdbd0..37366b9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -5,6 +5,7 @@ RUN cargo fetch COPY ./src ./src RUN rustup target add x86_64-unknown-linux-musl +RUN apt-get update && apt-get install -y musl-tools RUN cargo build --release --target x86_64-unknown-linux-musl FROM debian:bookworm-slim From acab86f39c37953315e920e0d9bcc1fe1c811e55 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:43:12 -0500 Subject: [PATCH 24/43] Fix path --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 37366b9..d18f8a9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -11,6 +11,6 @@ RUN cargo build --release --target x86_64-unknown-linux-musl FROM debian:bookworm-slim RUN apt-get update && apt-get install -y libc6 -COPY --from=builder target/release/backend /usr/local/bin/backend +COPY --from=builder target/x86_64-unknown-linux-musl/release/backend /usr/local/bin/backend CMD ["/usr/local/bin/backend"] \ No newline at end of file From 4419650eee3be94d628d5e922140f436e6951fb8 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 18:52:29 -0500 Subject: [PATCH 25/43] Non slim --- backend/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index d18f8a9..7d0b99a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:slim-bookworm AS builder +FROM rust:latest AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch @@ -8,7 +8,7 @@ RUN rustup target add x86_64-unknown-linux-musl RUN apt-get update && apt-get install -y musl-tools RUN cargo build --release --target x86_64-unknown-linux-musl -FROM debian:bookworm-slim +FROM debian:bookworm RUN apt-get update && apt-get install -y libc6 COPY --from=builder target/x86_64-unknown-linux-musl/release/backend /usr/local/bin/backend From fee9cd37db16a5a6a44c5927c3b7ed4802132bea Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 19:03:42 -0500 Subject: [PATCH 26/43] dockerignore --- backend/.dockerignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 backend/.dockerignore diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1 @@ +/target \ No newline at end of file From 0d86777e83c4fd64ef71ac98cea721d2ce182549 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 19:20:43 -0500 Subject: [PATCH 27/43] Revert dockerignore --- backend/.dockerignore | 1 - backend/Dockerfile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 backend/.dockerignore diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index c41cc9e..0000000 --- a/backend/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -/target \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 7d0b99a..a407838 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:latest AS builder +FROM rust:bookworm AS builder COPY ./Cargo.toml ./Cargo.lock ./ RUN cargo fetch From b143bef40cd70d30d762b35c3dd9e407d686cc42 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 19:26:53 -0500 Subject: [PATCH 28/43] alpine all --- backend/Dockerfile | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index a407838..aa5067b 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,16 +1,12 @@ -FROM rust:bookworm AS builder +FROM rust:1.83.0-alpine AS builder -COPY ./Cargo.toml ./Cargo.lock ./ -RUN cargo fetch -COPY ./src ./src +COPY . . -RUN rustup target add x86_64-unknown-linux-musl -RUN apt-get update && apt-get install -y musl-tools -RUN cargo build --release --target x86_64-unknown-linux-musl +RUN apk add --no-cache clang lld musl-dev git +RUN cargo build --release --locked -FROM debian:bookworm +FROM alpine:3.18 AS final -RUN apt-get update && apt-get install -y libc6 -COPY --from=builder target/x86_64-unknown-linux-musl/release/backend /usr/local/bin/backend +COPY --from=builder target/release/backend /usr/local/bin/backend CMD ["/usr/local/bin/backend"] \ No newline at end of file From 990b453c7322a55c3a562973818401a34c91cce7 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:02:41 -0500 Subject: [PATCH 29/43] Cleanup --- backend/src/delete_service.rs | 10 ++++++---- backend/src/main.rs | 11 +++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/backend/src/delete_service.rs b/backend/src/delete_service.rs index c314b85..848038f 100644 --- a/backend/src/delete_service.rs +++ b/backend/src/delete_service.rs @@ -64,10 +64,12 @@ impl DeleteHandler { if let Err(err) = mongo_service.delete_paste(paste_id).await { error!("Failed to delete paste from database: {:?}", err); } - if let id = &paste.id { - if let Err(err) = mongo_service.delete_paste(id).await { - error!("Failed to delete paste file: {}", err); - } + let id = &paste.id; + + if let Err(err) = mongo_service.delete_paste(id).await { + error!("Failed to delete paste file: {}", err); + } else { + warn!("Deleted paste file: {}", id); } } diff --git a/backend/src/main.rs b/backend/src/main.rs index 569b1f3..9e11a9c 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -11,11 +11,11 @@ use crate::controllers::get_controller::{get_content_handler, get_metadata_handl use crate::controllers::upload_controller::upload_handler; use crate::delete_service::DeleteHandler; use crate::mongodb_service::MongoService; -use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use actix_cors::Cors; +use actix_web::{web, App, HttpServer}; use dotenv::dotenv; use std::env; use std::sync::Arc; -use actix_cors::Cors; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -52,7 +52,7 @@ async fn main() -> std::io::Result<()> { let delete_handler = DeleteHandler::new(Arc::clone(&aws_service), Arc::clone(&mongo_service)); delete_handler.start_delete_loop(); - + HttpServer::new(move || { let cors = Cors::permissive(); @@ -67,9 +67,4 @@ async fn main() -> std::io::Result<()> { .bind(("0.0.0.0", 8080))? .run() .await -} - -async fn health_check() -> impl Responder { - println!("Health check"); - HttpResponse::Ok().body("OK") } \ No newline at end of file From a0c2b85910b80d920134ec6cc4032ca1c08889e6 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:10:04 -0500 Subject: [PATCH 30/43] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd6af11..e9e5504 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # PasteBook -An easy on the eyes, portable, lightning fast pastebin written in Svelte and Kotlin. +An easy on the eyes, portable, lightning fast pastebin written in Svelte and Rust. ### Prerequisites Docker. Both the frontend and backend are to be installed with Docker. You can learn more [here](https://www.docker.com). @@ -73,9 +73,11 @@ systemctl restart nginx Run the following commands in succession. ```bash docker compose stop +docker compose pull docker compose up -d ``` # Final Notes Wow. There was a lot that can go wrong there. I'm not an expert. If you need help, you can email me at contact@loudbook.dev or find me elsewhere. + 💜 From af53acdbb9e80a58cf527e7e33483fe78d530d38 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:10:30 -0500 Subject: [PATCH 31/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 581a98c..dcac62e 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -2,9 +2,9 @@ name: Build and Publish Docker Images on: push: - # paths: - # - frontend/** - # - backend/** + paths: + - frontend/** + - backend/** branches: - master - dev From 0029c9eab6ee3aa89a053a75b6d27f7fbc48f89c Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:24:36 -0500 Subject: [PATCH 32/43] Rename to expiresAt --- backend/src/controllers/upload_controller.rs | 2 +- backend/src/delete_service.rs | 2 +- backend/src/models/paste.rs | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/upload_controller.rs b/backend/src/controllers/upload_controller.rs index 61ed386..57002a1 100644 --- a/backend/src/controllers/upload_controller.rs +++ b/backend/src/controllers/upload_controller.rs @@ -69,7 +69,7 @@ pub async fn upload_handler( report_book, wrap, creator_ip: ip.clone(), - expires, + expires_at: expires, }; if let Err(e) = aws_service.put_file(&file_id, (&body).as_ref()).await { diff --git a/backend/src/delete_service.rs b/backend/src/delete_service.rs index 848038f..55ffef2 100644 --- a/backend/src/delete_service.rs +++ b/backend/src/delete_service.rs @@ -51,7 +51,7 @@ impl DeleteHandler { while let Some(paste) = pastes_cursor.next().await { let paste = paste.expect("Failed to get paste"); - if paste.expires > 0 && paste.expires < now as u64 { + if paste.expires_at > 0 && paste.expires_at < now as u64 { deletable_pastes.push(paste.clone()); } diff --git a/backend/src/models/paste.rs b/backend/src/models/paste.rs index 2786c4d..930c642 100644 --- a/backend/src/models/paste.rs +++ b/backend/src/models/paste.rs @@ -9,7 +9,8 @@ pub struct Paste { pub report_book: bool, pub wrap: bool, pub creator_ip: String, - pub expires: u64, + #[serde(rename = "expiresAt")] + pub expires_at: u64, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -20,7 +21,8 @@ pub struct PasteDTO { pub created: u64, pub report_book: bool, pub wrap: bool, - pub expires: u64, + #[serde(rename = "expiresAt")] + pub expires_at: u64, } impl Paste { @@ -32,7 +34,7 @@ impl Paste { created: self.created, report_book: self.report_book, wrap: self.wrap, - expires: self.expires, + expires_at: self.expires_at, } } } From 7e8c0278cb676254c3001e4801fbd5c6885bf442 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:33:38 -0500 Subject: [PATCH 33/43] Change to matrix build --- .github/workflows/build-and-deploy.yml | 129 +++++++++++-------------- 1 file changed, 55 insertions(+), 74 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index dcac62e..094dbf0 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -8,89 +8,70 @@ on: branches: - master - dev - + jobs: build: runs-on: ubuntu-latest permissions: packages: write - + strategy: + matrix: + component: [frontend, backend] steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 - - name: Log in to GHCR - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Get Commit Hash - id: get_commit_hash - run: echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + - name: Log in to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Set Docker Tags - id: set_tags - run: | - if [ "${{ github.ref_name }}" == "master" ]; then - echo "tag=latest" >> $GITHUB_ENV - elif [ "${{ github.ref_name }}" == "dev" ]; then - echo "tag=dev" >> $GITHUB_ENV - else - echo "Unknown branch: ${{ github.ref_name }}" - exit 1 - fi - - - name: Build and push frontend Docker image to GHCR - uses: docker/build-push-action@v3 - with: - context: ./frontend - file: ./frontend/Dockerfile - build-args: | - COMMIT_HASH=${{ env.hash }} - push: true - tags: | - ghcr.io/loudbooks/pastebook-frontend:${{ env.tag }} - ghcr.io/loudbooks/pastebook-frontend:${{ env.hash }} + - name: Get Commit Hash + id: get_commit_hash + run: echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: Build and push backend Docker image to GHCR - uses: docker/build-push-action@v3 - with: - context: ./backend - file: ./backend/Dockerfile - push: true - tags: | - ghcr.io/loudbooks/pastebook-backend:${{ env.tag }} - ghcr.io/loudbooks/pastebook-backend:${{ env.hash }} + - name: Set Docker Tags + id: set_tags + run: | + if [ "${{ github.ref_name }}" == "master" ]; then + echo "tag=latest" >> $GITHUB_ENV + elif [ "${{ github.ref_name }}" == "dev" ]; then + echo "tag=dev" >> $GITHUB_ENV + else + echo "Unknown branch: ${{ github.ref_name }}" + exit 1 - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_TOKEN }} + - name: Build and push Docker image to GHCR + uses: docker/build-push-action@v3 + with: + context: ./${{ matrix.component }} + file: ./${{ matrix.component }}/Dockerfile + build-args: | + COMMIT_HASH=${{ env.hash }} + push: true + tags: | + ghcr.io/loudbooks/pastebook-${{ matrix.component }}:${{ env.tag }} + ghcr.io/loudbooks/pastebook-${{ matrix.component }}:${{ env.hash }} - - name: Build and push frontend Docker image to Docker Hub - uses: docker/build-push-action@v3 - with: - context: ./frontend - build-args: | - COMMIT_HASH=${{ env.hash }} - file: ./frontend/Dockerfile - push: true - tags: | - ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.tag }} - ${{ secrets.DOCKER_USERNAME }}/pastebook-frontend:${{ env.hash }} + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} - - name: Build and push backend Docker image to Docker Hub - uses: docker/build-push-action@v3 - with: - context: ./backend - file: ./backend/Dockerfile - push: true - tags: | - ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.tag }} - ${{ secrets.DOCKER_USERNAME }}/pastebook-backend:${{ env.hash }} + - name: Build and push Docker image to Docker Hub + uses: docker/build-push-action@v3 + with: + context: ./${{ matrix.component }} + file: ./${{ matrix.component }}/Dockerfile + build-args: | + COMMIT_HASH=${{ env.hash }} + push: true + tags: | + ${{ secrets.DOCKER_USERNAME }}/pastebook-${{ matrix.component }}:${{ env.tag }} + ${{ secrets.DOCKER_USERNAME }}/pastebook-${{ matrix.component }}:${{ env.hash }} From 1d6956fb19769e7e4cf2439284235d6ebd60f7fe Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:38:15 -0500 Subject: [PATCH 34/43] Don't need to rename --- backend/src/models/paste.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/models/paste.rs b/backend/src/models/paste.rs index 930c642..3f3d316 100644 --- a/backend/src/models/paste.rs +++ b/backend/src/models/paste.rs @@ -9,7 +9,6 @@ pub struct Paste { pub report_book: bool, pub wrap: bool, pub creator_ip: String, - #[serde(rename = "expiresAt")] pub expires_at: u64, } @@ -21,7 +20,6 @@ pub struct PasteDTO { pub created: u64, pub report_book: bool, pub wrap: bool, - #[serde(rename = "expiresAt")] pub expires_at: u64, } From 1dd34cb868e2d5d8a3e61c2acf9bce0259dd7a9e Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:47:46 -0500 Subject: [PATCH 35/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 094dbf0..995f346 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -45,6 +45,7 @@ jobs: else echo "Unknown branch: ${{ github.ref_name }}" exit 1 + fi - name: Build and push Docker image to GHCR uses: docker/build-push-action@v3 From f3a4712331d3b40645c8f5c432bd062b44de303c Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:48:22 -0500 Subject: [PATCH 36/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 995f346..928945e 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -1,6 +1,7 @@ name: Build and Publish Docker Images on: + workflow_dispatch: push: paths: - frontend/** From e0e93e263350c427bdd2ba83229be47a45cfd553 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 20:50:13 -0500 Subject: [PATCH 37/43] No need for dotenv --- backend/Cargo.lock | 7 ------- backend/Cargo.toml | 1 - backend/src/main.rs | 2 -- 3 files changed, 10 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 13c9fb2..940d7e5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -622,7 +622,6 @@ dependencies = [ "aws-credential-types", "aws-sdk-s3", "chrono", - "dotenv", "env_logger", "flate2", "futures-util", @@ -1043,12 +1042,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "ecdsa" version = "0.14.8" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f60dc62..ef6e734 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -15,7 +15,6 @@ flate2 = "1.0.35" chrono = "0.4.39" rand = "0.8.5" log = "0.4.22" -dotenv = "0.15" futures-util = "0.3.31" aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } env_logger = "0.11.5" diff --git a/backend/src/main.rs b/backend/src/main.rs index 9e11a9c..d5cf34a 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -13,14 +13,12 @@ use crate::delete_service::DeleteHandler; use crate::mongodb_service::MongoService; use actix_cors::Cors; use actix_web::{web, App, HttpServer}; -use dotenv::dotenv; use std::env; use std::sync::Arc; #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); - dotenv().ok(); let database_url = env::var("S3_ENDPOINT").expect("S3_ENDPOINT must be set"); let aws_access_key = env::var("S3_ACCESS_KEY_ID").expect("S3_ACCESS_KEY_ID must be set"); From efd6e0b8c13d644acac059222634e7b004f8dd91 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Tue, 10 Dec 2024 22:02:13 -0500 Subject: [PATCH 38/43] Fewer dependencies --- backend/Cargo.lock | 18 ++++++++++++++++++ backend/Cargo.toml | 10 +++++----- backend/src/delete_service.rs | 4 ++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 940d7e5..bce6f24 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1153,6 +1153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] @@ -1786,6 +1787,17 @@ version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2907,6 +2919,12 @@ dependencies = [ "serde", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ef6e734..2ab4c49 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -8,16 +8,16 @@ actix-web = "4" actix-cors = "0.7.0" mongodb = "3.1.0" aws-sdk-s3 = { version = "1.65.0" } -tokio = { version = "1", features = ["full"] } -anyhow = "1.0.94" +tokio = { version = "1", features = ["rt"] } +anyhow = { version = "1.0.40", default-features = false } serde = { version = "1.0.215", features = ["derive"] } -flate2 = "1.0.35" -chrono = "0.4.39" +flate2 = { version = "1.0.19", features = ["zlib"] } +chrono = { version = "0.4.19", features = ["serde"] } rand = "0.8.5" -log = "0.4.22" futures-util = "0.3.31" aws-credential-types = { version = "1.2.1", features = ["hardcoded-credentials"] } env_logger = "0.11.5" +log = "0.4.22" [[bin]] name = "backend" diff --git a/backend/src/delete_service.rs b/backend/src/delete_service.rs index 55ffef2..8f59397 100644 --- a/backend/src/delete_service.rs +++ b/backend/src/delete_service.rs @@ -2,9 +2,9 @@ use std::sync::Arc; use std::time::Duration; use chrono::Utc; use futures_util::StreamExt; -use log::{error, warn}; use tokio::spawn; use tokio::time::interval; +use log::{error, warn}; use crate::aws_service::AWSService; use crate::mongodb_service::MongoService; @@ -65,7 +65,7 @@ impl DeleteHandler { error!("Failed to delete paste from database: {:?}", err); } let id = &paste.id; - + if let Err(err) = mongo_service.delete_paste(id).await { error!("Failed to delete paste file: {}", err); } else { From ffeefca1a6930110905786329fd74732c07a6d27 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Wed, 11 Dec 2024 09:41:35 -0500 Subject: [PATCH 39/43] Test MacOS Build --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 928945e..9e72e19 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: macos-latest permissions: packages: write strategy: From b43ca5b98680b2a1284a70033e9fa4ce59887b5e Mon Sep 17 00:00:00 2001 From: Loudbook Date: Wed, 11 Dec 2024 09:43:05 -0500 Subject: [PATCH 40/43] Update build-and-deploy.yml --- .github/workflows/build-and-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 9e72e19..928945e 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: macos-latest + runs-on: ubuntu-latest permissions: packages: write strategy: From ee764ae49a887b8a69ea58b463827fd188bcc47e Mon Sep 17 00:00:00 2001 From: Loudbook Date: Wed, 11 Dec 2024 14:15:56 -0500 Subject: [PATCH 41/43] MongoDB Migration --- backend/src/controllers/get_controller.rs | 4 +- backend/src/controllers/upload_controller.rs | 4 +- backend/src/{ => database}/aws_service.rs | 0 backend/src/database/migration_service.rs | 136 ++++++++++++++++++ backend/src/database/mod.rs | 5 + backend/src/{ => database}/mongodb_service.rs | 9 +- backend/src/{ => database}/mongoresult.rs | 0 backend/src/delete_service.rs | 4 +- backend/src/main.rs | 8 +- 9 files changed, 157 insertions(+), 13 deletions(-) rename backend/src/{ => database}/aws_service.rs (100%) create mode 100644 backend/src/database/migration_service.rs create mode 100644 backend/src/database/mod.rs rename backend/src/{ => database}/mongodb_service.rs (86%) rename backend/src/{ => database}/mongoresult.rs (100%) diff --git a/backend/src/controllers/get_controller.rs b/backend/src/controllers/get_controller.rs index bcc6e9e..3264802 100644 --- a/backend/src/controllers/get_controller.rs +++ b/backend/src/controllers/get_controller.rs @@ -1,10 +1,10 @@ -use crate::aws_service::AWSService; +use crate::database::aws_service::AWSService; use actix_web::{ web, HttpRequest, HttpResponse, Responder, }; use serde::Deserialize; use std::sync::Arc; -use crate::mongodb_service::MongoService; +use crate::database::mongodb_service::MongoService; #[derive(Deserialize)] pub struct ContentQuery { diff --git a/backend/src/controllers/upload_controller.rs b/backend/src/controllers/upload_controller.rs index 57002a1..7f178c0 100644 --- a/backend/src/controllers/upload_controller.rs +++ b/backend/src/controllers/upload_controller.rs @@ -1,6 +1,6 @@ -use crate::aws_service::AWSService; +use crate::database::aws_service::AWSService; use crate::models::paste::Paste; -use crate::mongodb_service::MongoService; +use crate::database::mongodb_service::MongoService; use crate::utils::iputils::IPUtils; use actix_web::{web, HttpRequest, HttpResponse, Responder}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; diff --git a/backend/src/aws_service.rs b/backend/src/database/aws_service.rs similarity index 100% rename from backend/src/aws_service.rs rename to backend/src/database/aws_service.rs diff --git a/backend/src/database/migration_service.rs b/backend/src/database/migration_service.rs new file mode 100644 index 0000000..412a335 --- /dev/null +++ b/backend/src/database/migration_service.rs @@ -0,0 +1,136 @@ +use crate::models::paste::Paste; +use crate::models::user::User; +use futures_util::StreamExt; +use mongodb::bson::{doc, Document}; +use mongodb::{Collection, Database}; + +pub struct MigrationService { + database: Database, + admin_database: Database, + users_collection: Collection, + pastes_collection: Collection, +} + +impl MigrationService { + pub fn new(database: &Database, admin_database: &Database, users_collection: &Collection, pastes_collection: &Collection) -> Self { + Self { + database: database.clone(), + admin_database: admin_database.clone(), + users_collection: users_collection.clone(), + pastes_collection: pastes_collection.clone(), + } + } + + pub async fn run_migrations(&self) { + self.user_migration_12_11_24().await.map_err(|e| println!("Error migrating users: {}", e)).ok(); + self.paste_migration_12_11_24().await.map_err(|e| println!("Error migrating pastes: {}", e)).ok(); + } + + pub async fn user_migration_12_11_24(&self) -> Result<(), mongodb::error::Error> { + if !self.database.list_collection_names().await?.contains(&"user".to_string()) { + println!("No users to migrate."); + return Ok(()); + } + + println!("Migrating users..."); + let old_user_collection = self.database.collection::("user"); + + let mut amount = 0; + let target_amount = old_user_collection.count_documents(doc! {}).await?; + let mut cursor = old_user_collection.find(doc! {}).await?; + + while let Some(old_user) = cursor.next().await { + let old_user = old_user?; + let user_ip = old_user.get_str("_id").unwrap_or_default(); + let user_id = old_user.get_str("id").unwrap_or_default(); + let user_requests = old_user.get_i32("requests").unwrap_or_default(); + let user_banned = old_user.get_bool("banned").unwrap_or_default(); + let last_visit = old_user.get_i64("lastVisit").unwrap_or_default(); + + if user_ip.is_empty() || user_id.is_empty() { + println!("Skipping user with empty IP or ID."); + continue; + } + + let user = User { + ip: user_ip.to_string(), + id: user_id.to_string(), + requests: user_requests as u64, + created_at: last_visit, + banned: user_banned, + }; + + self.users_collection.insert_one(user).await?; + + amount += 1; + } + + println!("Migrated {} out of {} users", amount, target_amount); + + let qualified_old_database_name = self.database.name().to_owned() + ".user"; + let qualified_new_database_name = self.database.name().to_owned() + ".user_migrated_12_11_24"; + + self.admin_database.run_command(doc! { + "renameCollection": qualified_old_database_name, + "to": qualified_new_database_name, + "dropTarget": true + }).await?; + + Ok(()) + } + + pub async fn paste_migration_12_11_24(&self) -> Result<(), mongodb::error::Error> { + if !self.database.list_collection_names().await?.contains(&"pastePrivateDTO".to_string()) { + println!("No pastes to migrate."); + } + + println!("Migrating pastes..."); + let old_paste_collection = self.database.collection::("pastePrivateDTO"); + + let mut amount = 0; + let target_amount = old_paste_collection.count_documents(doc! {}).await?; + let mut cursor = old_paste_collection.find(doc! {}).await?; + + while let Some(old_paste) = cursor.next().await { + let old_paste = old_paste?; + let paste_id = old_paste.get_str("_id").unwrap_or_default(); + let paste_expires_at = old_paste.get_i64("expires").unwrap_or_default(); + let paste_created_at = old_paste.get_i64("created").unwrap_or_default(); + let paste_creator_ip = old_paste.get_str("creatorIP").unwrap_or_default(); + let paste_report_book = old_paste.get_bool("reportBook").unwrap_or_default(); + let paste_wrap = old_paste.get_bool("wrap").unwrap_or_default(); + + if paste_id.is_empty() { + println!("Skipping paste with empty ID."); + continue; + } + + let paste = Paste { + id: paste_id.to_string(), + title: "".to_string(), + created: paste_created_at as u64, + report_book: paste_report_book, + wrap: paste_wrap, + creator_ip: paste_creator_ip.to_string(), + expires_at: paste_expires_at as u64, + }; + + self.pastes_collection.insert_one(paste).await?; + + amount += 1; + } + + println!("Migrated {} out of {} pastes", amount, target_amount); + + let qualified_old_database_name = self.database.name().to_owned() + ".pastePrivateDTO"; + let qualified_new_database_name = self.database.name().to_owned() + ".pastePrivateDTO_migrated_12_11_24"; + + self.admin_database.run_command(doc! { + "renameCollection": qualified_old_database_name, + "to": qualified_new_database_name, + "dropTarget": true + }).await?; + + Ok(()) + } +} \ No newline at end of file diff --git a/backend/src/database/mod.rs b/backend/src/database/mod.rs new file mode 100644 index 0000000..ffa89f5 --- /dev/null +++ b/backend/src/database/mod.rs @@ -0,0 +1,5 @@ +pub mod aws_service; +pub mod mongodb_service; +pub mod mongoresult; + +mod migration_service; \ No newline at end of file diff --git a/backend/src/mongodb_service.rs b/backend/src/database/mongodb_service.rs similarity index 86% rename from backend/src/mongodb_service.rs rename to backend/src/database/mongodb_service.rs index db2e1cc..a56690d 100644 --- a/backend/src/mongodb_service.rs +++ b/backend/src/database/mongodb_service.rs @@ -1,9 +1,10 @@ use mongodb::bson::{doc, uuid}; use mongodb::{Client, Collection, Cursor}; use mongodb::options::ClientOptions; +use crate::database::migration_service::MigrationService; use crate::models::paste::Paste; use crate::models::user::User; -use crate::mongoresult::MongoResult; +use crate::database::mongoresult::MongoResult; pub struct MongoService { user_collection: Collection, @@ -17,10 +18,14 @@ impl MongoService { let client = Client::with_options(client_options)?; let database = client.database(db_name); + let admin_database = client.database("admin"); let user_collection = database.collection::("users"); let paste_collection = database.collection::("pastes"); println!("Connected to MongoDB"); + + let migration_service = MigrationService::new(&database, &admin_database, &user_collection, &paste_collection); + migration_service.run_migrations().await; Ok(Self { user_collection, @@ -33,7 +38,7 @@ impl MongoService { let update = doc! { "$inc": { "requests": 1 }, "$setOnInsert": { - "id": uuid::Uuid::new().to_string(), + "_id": uuid::Uuid::new().to_string(), "created_at": chrono::Utc::now().timestamp_millis(), "banned": false } diff --git a/backend/src/mongoresult.rs b/backend/src/database/mongoresult.rs similarity index 100% rename from backend/src/mongoresult.rs rename to backend/src/database/mongoresult.rs diff --git a/backend/src/delete_service.rs b/backend/src/delete_service.rs index 8f59397..aa02abc 100644 --- a/backend/src/delete_service.rs +++ b/backend/src/delete_service.rs @@ -5,8 +5,8 @@ use futures_util::StreamExt; use tokio::spawn; use tokio::time::interval; use log::{error, warn}; -use crate::aws_service::AWSService; -use crate::mongodb_service::MongoService; +use crate::database::aws_service::AWSService; +use crate::database::mongodb_service::MongoService; pub struct DeleteHandler { aws_service: Arc, diff --git a/backend/src/main.rs b/backend/src/main.rs index d5cf34a..5ebf303 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,16 +1,14 @@ -mod aws_service; -mod mongodb_service; mod models; mod controllers; -mod mongoresult; mod utils; mod delete_service; +mod database; -use crate::aws_service::AWSService; +use database::aws_service::AWSService; use crate::controllers::get_controller::{get_content_handler, get_metadata_handler}; use crate::controllers::upload_controller::upload_handler; use crate::delete_service::DeleteHandler; -use crate::mongodb_service::MongoService; +use database::mongodb_service::MongoService; use actix_cors::Cors; use actix_web::{web, App, HttpServer}; use std::env; From 36f2f275b07b7fee036b25590215aea15128b2d2 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Wed, 11 Dec 2024 14:23:59 -0500 Subject: [PATCH 42/43] Update expires tag --- .gitignore | 3 ++- frontend/src/lib/paste.ts | 2 +- frontend/src/routes/[pastes=paste]/[slug]/+page.svelte | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b89a4cd..c3f7faf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .* !.gitignore docker-compose.yml -.idea \ No newline at end of file +.idea +/backend/target diff --git a/frontend/src/lib/paste.ts b/frontend/src/lib/paste.ts index 98a486c..105ce2b 100755 --- a/frontend/src/lib/paste.ts +++ b/frontend/src/lib/paste.ts @@ -7,5 +7,5 @@ export type Paste = { user: { id: string; }; - expiresAt: string; + expires_at: string; }; diff --git a/frontend/src/routes/[pastes=paste]/[slug]/+page.svelte b/frontend/src/routes/[pastes=paste]/[slug]/+page.svelte index c672143..6186d8f 100755 --- a/frontend/src/routes/[pastes=paste]/[slug]/+page.svelte +++ b/frontend/src/routes/[pastes=paste]/[slug]/+page.svelte @@ -28,7 +28,7 @@ reportBook = data.reportBook; wrap = data.wrap; userId = data.user.id; - expires = new Date(data.expiresAt) + expires = new Date(data.expires_at); const reloadTime = () => { timeSinceStr = formatTimeSince(created as unknown as number); From 451ce149219fa081e6e8f1f2e7a34bfe85f38973 Mon Sep 17 00:00:00 2001 From: Loudbook Date: Wed, 11 Dec 2024 14:27:06 -0500 Subject: [PATCH 43/43] Missing return statement --- backend/src/database/aws_service.rs | 2 +- backend/src/database/migration_service.rs | 1 + backend/src/database/mongodb_service.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/database/aws_service.rs b/backend/src/database/aws_service.rs index 127c9ba..7a5073f 100644 --- a/backend/src/database/aws_service.rs +++ b/backend/src/database/aws_service.rs @@ -25,7 +25,7 @@ impl AWSService { let client = Client::from_conf(config); - println!("Connected to AWS S3"); + println!("Connected to AWS S3."); Ok(Self { client, diff --git a/backend/src/database/migration_service.rs b/backend/src/database/migration_service.rs index 412a335..65d404b 100644 --- a/backend/src/database/migration_service.rs +++ b/backend/src/database/migration_service.rs @@ -82,6 +82,7 @@ impl MigrationService { pub async fn paste_migration_12_11_24(&self) -> Result<(), mongodb::error::Error> { if !self.database.list_collection_names().await?.contains(&"pastePrivateDTO".to_string()) { println!("No pastes to migrate."); + return Ok(()); } println!("Migrating pastes..."); diff --git a/backend/src/database/mongodb_service.rs b/backend/src/database/mongodb_service.rs index a56690d..771ec2c 100644 --- a/backend/src/database/mongodb_service.rs +++ b/backend/src/database/mongodb_service.rs @@ -22,7 +22,7 @@ impl MongoService { let user_collection = database.collection::("users"); let paste_collection = database.collection::("pastes"); - println!("Connected to MongoDB"); + println!("Connected to MongoDB."); let migration_service = MigrationService::new(&database, &admin_database, &user_collection, &paste_collection); migration_service.run_migrations().await;