diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 0000000..af04248 --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,27 @@ +name: "Test" + +on: + push: + branches: + - "dev" + +jobs: + push-to-registry: + name: "Build and push Docker image to Docker Hub" + runs-on: "ubuntu-latest" + steps: + - name: "Check out the repo" + uses: actions/checkout@v3 + + - name: "Log in to Docker Hub" + uses: "docker/login-action@v2" + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: "Build and push Docker image" + uses: "docker/build-push-action@v3" + with: + context: . + push: true + tags: clowzed/sero:dev-unstable diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e46b2cc..9d1eb30 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -104,3 +104,53 @@ jobs: push: true tags: clowzed/sero:v${{ needs.get-tag.outputs.pkg-version }} labels: ${{ steps.meta.outputs.labels }} + upload-openapi-client: + needs: + - "push-to-registry" + name: "Build and upload openapi python client" + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: "16" + + - name: Install OpenAPI Generator CLI + run: | + npm install @openapitools/openapi-generator-cli -g + - name: Extract version from OpenAPI spec + id: extract_version + run: | + VERSION=$(jq -r '.info.version' openapi.json) + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Generate Python client from OpenAPI spec + run: | + openapi-generator-cli generate -i openapi.json -g python -o seroapi --additional-properties=packageName=seroapi,packageVersion=${{ env.VERSION }} + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + + - name: Configure Poetry + run: | + poetry config virtualenvs.in-project true + + - name: Install dependencies + run: | + cd seroapi + poetry install + + - name: Publish to Test PyPI + run: | + cd seroapi + poetry publish -u __token__ -p ${{ secrets.PYPI_PASSWORD }} --build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5556be5..2a2e902 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,24 +46,6 @@ jobs: steps: - uses: actions/checkout@v3 - - name: "Get version from Cargo.toml" - id: "get-cargo-version" - shell: "bash" - run: | - echo PKG_VERSION=$(awk -F ' = ' '$1 ~ /version/ { gsub(/["]/, "", $2); printf("%s",$2) }' Cargo.toml) >> $GITHUB_OUTPUT - - name: Get version from openapi.json - id: get-openapi-version - run: | - echo OAPI_VERSION=$(jq -r '.info.version' openapi.json) >> $GITHUB_OUTPUT - - - name: Compare versions - run: | - if [ "${{ steps.get-cargo-version.outputs.PKG_VERSION }}" != "${{ steps.get-openapi-version.outputs.OAPI_VERSION }}" ]; then - echo "Version mismatch between cargo.toml and generated OpenAPI JSON." - exit 1 - else - echo "Version matches between cargo.toml and generated OpenAPI JSON." - fi - name: Run tests (with database service) run: cargo test --verbose -- --test-threads=1 @@ -77,14 +59,14 @@ jobs: - uses: "actions-rs/toolchain@v1" with: profile: "minimal" - toolchain: "stable" + toolchain: "nightly" override: true - run: "rustup component add rustfmt" - uses: "actions-rs/cargo@v1" with: - command: "+nightly fmt" + command: "fmt" args: "--all -- --check" clippy: diff --git a/.gitignore b/.gitignore index 3337113..99dc2ec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ sites-uploads logs test_upload_files .env +openapi.json diff --git a/Cargo.toml b/Cargo.toml index c5bf411..80b0906 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sero" -version = "0.2.1" +version = "0.2.8" edition = "2021" authors = ["clowzed "] description = "Muiltidomain static site hosting" @@ -11,19 +11,19 @@ license = "MIT" [dependencies] envy = "0.4.2" sea-orm = { version = "0.12.3", features = [ - "sqlx-postgres", - "runtime-tokio-rustls", - "macros", + "sqlx-postgres", + "runtime-tokio-rustls", + "macros", ] } tokio = { version = "1.32.0", features = ["full"] } tokio-postgres = "0.7.10" tracing = { version = "0.1.37", features = ["async-await"] } tracing-subscriber = { version = "0.3.17", features = [ - "env-filter", - "fmt", - "ansi", - "std", - "json", + "env-filter", + "fmt", + "ansi", + "std", + "json", ] } entity = { path = "entity" } migration = { path = "migration" } @@ -41,9 +41,9 @@ mime = "0.3.17" mime_guess = "2.0.4" argon2 = { version = "0.5.3", features = ["std"] } utoipa = { version = "4.2.0", features = [ - "axum_extras", - "chrono", - "preserve_order", + "axum_extras", + "chrono", + "preserve_order", ] } dotenvy = "0.15.7" toml = "0.8.8" @@ -52,16 +52,16 @@ utoipa-rapidoc = { version = "4.0.0", features = ["axum"] } utoipa-redoc = { version = "4.0.0", features = ["axum"] } utoipa-swagger-ui = { version = "7.1.0", features = ["axum"] } axum = { version = "0.7.4", features = [ - "macros", - "tracing", - "json", - "multipart", + "macros", + "tracing", + "json", + "multipart", ] } axum_typed_multipart = "0.11.0" tower-http = { git = "https://github.com/tower-rs/tower-http.git", features = [ - "cors", - "trace", - "timeout", + "cors", + "trace", + "timeout", ] } tower = { version = "0.4.13", features = ["util"] } hyper = "0.14.28" @@ -77,5 +77,11 @@ members = [".", "entity", "migration"] [dev-dependencies] - axum-test = "15.2.0" + + +[profile.release] +lto = true +strip = true +opt-level = 3 +codegen-units = 1 diff --git a/assets/zips/correct-1.zip b/assets/zips/correct-1.zip index 6c82e27..7842288 100644 Binary files a/assets/zips/correct-1.zip and b/assets/zips/correct-1.zip differ diff --git a/assets/zips/correct-2.zip b/assets/zips/correct-2.zip index 939621c..f30aa57 100644 Binary files a/assets/zips/correct-2.zip and b/assets/zips/correct-2.zip differ diff --git a/assets/zips/correct-3.zip b/assets/zips/correct-3.zip index 0e9c53c..2641e25 100644 Binary files a/assets/zips/correct-3.zip and b/assets/zips/correct-3.zip differ diff --git a/assets/zips/correct-with-404.html.zip b/assets/zips/correct-with-404.html.zip index 9b17c0b..daec77b 100644 Binary files a/assets/zips/correct-with-404.html.zip and b/assets/zips/correct-with-404.html.zip differ diff --git a/assets/zips/correct-with-503.html.zip b/assets/zips/correct-with-503.html.zip index 5a66f25..a6cf06f 100644 Binary files a/assets/zips/correct-with-503.html.zip and b/assets/zips/correct-with-503.html.zip differ diff --git a/assets/zips/correct-without-404.html.zip b/assets/zips/correct-without-404.html.zip index 8c9eb8b..7842288 100644 Binary files a/assets/zips/correct-without-404.html.zip and b/assets/zips/correct-without-404.html.zip differ diff --git a/assets/zips/correct-without-503.html.zip b/assets/zips/correct-without-503.html.zip index 65e4f40..cfc3724 100644 Binary files a/assets/zips/correct-without-503.html.zip and b/assets/zips/correct-without-503.html.zip differ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..d5091aa --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,69 @@ +version: "3" + +services: + database: + image: postgres:16 + user: postgres + environment: + - POSTGRES_USER=postgres + - POSTGRES_DB=sero + - POSTGRES_PASSWORD=1234 + + healthcheck: + test: ["CMD-SHELL", "pg_isready"] + interval: 5s + timeout: 5s + retries: 5 + volumes: + - pgdata:/var/lib/postgresql/data + + + proxy: + image: nginx:alpine3.18-slim + environment: + - DOLLAR=$ + - SERVER_PORT=8080 + - SERVER=server + # Edit this + - DOMAIN= + - ZONE= + # End of edit + volumes: + - ./nginx-templates:/etc/nginx/templates + ports: + - 443:443 + - 80:80 + links: + - server + depends_on: + - server + + server: + image: clowzed/sero:dev-unstable + build: . + depends_on: + database: + condition: service_healthy + volumes: + - server-files:/app/sites-uploads + ports: + - 8080:8080 + environment: + - DATABASE_URL=postgresql://postgres:1234@database/sero + - PORT=8080 + # You can edit this section + # Empty means no limits + - MAX_USERS=1 + - MAX_SITES_PER_USER=100 + - MAX_BODY_LIMIT_SIZE=10000000 # 10mb + - RUST_LOG=none,sero=trace + - JWT_SECRET=mysuperstrongjwtscret + # end of section + - JWT_TTL_SECONDS=120 + - SQLX_LOGGING=true + - UPLOAD_FOLDER=./sites-uploads + + +volumes: + server-files: + pgdata: diff --git a/docker-compose.yml b/docker-compose.yml index f7727d8..8c1be93 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,6 @@ services: database: image: postgres:16 user: postgres - restart: always environment: - POSTGRES_USER=postgres - POSTGRES_DB=sero @@ -21,7 +20,6 @@ services: proxy: image: nginx:alpine3.18-slim - restart: always environment: - DOLLAR=$ - SERVER_PORT=8080 @@ -37,20 +35,17 @@ services: - 80:80 links: - server - profiles: - - donotstart + depends_on: + - server server: - image: clowzed/sero - restart: always + image: clowzed/sero:v0.2.7 build: . depends_on: database: condition: service_healthy volumes: - server-files:/app/sites-uploads - ports: - - 8080:8080 environment: - DATABASE_URL=postgresql://postgres:1234@database/sero - PORT=8080 @@ -60,8 +55,8 @@ services: - MAX_SITES_PER_USER=100 - MAX_BODY_LIMIT_SIZE=10000000 # 10mb - RUST_LOG=none,sero=trace - # end of section - JWT_SECRET=mysuperstrongjwtscret + # end of section - JWT_TTL_SECONDS=120 - SQLX_LOGGING=true - UPLOAD_FOLDER=./sites-uploads diff --git a/nginx-templates/default.conf.template b/nginx-templates/default.conf.template index d0f5380..bf3719b 100644 --- a/nginx-templates/default.conf.template +++ b/nginx-templates/default.conf.template @@ -1,18 +1,22 @@ +map $http_host $subdomain { + ~^(?[a-zA-Z0-9-]+)\.${DOMAIN}\.${ZONE}${DOLLAR} $subdomain; +} + server { - - - # We check if $subdomain is not empty and if the X-subdomain header is not present in the request - # ($http_x_subdomain = ""). - # If both conditions are met, we set the X-subdomain header using proxy_set_header. - # Otherwise, we do nothing, and the existing X-subdomain header (if any) will be preserved. - listen 80; - server_name ~^(?\w*)\.${DOMAIN}.${ZONE}${DOLLAR}; + server_name ~^(?[a-zA-Z0-9-]+)\.${DOMAIN}\.${ZONE}${DOLLAR}; location / { - if ($subdomain != "" && $http_x_subdomain = "") { - proxy_set_header x-subdomain $subdomain; - } - proxy_pass http://${SERVER}:${SERVER_PORT}/; + proxy_set_header X-Subdomain $subdomain; + proxy_pass http://${SERVER}:${SERVER_PORT}; + } +} + +server { + listen 80; + server_name ${DOMAIN}.${ZONE}; + + location / { + proxy_pass http://${SERVER}:${SERVER_PORT}; } } diff --git a/openapi.json b/openapi.json deleted file mode 100644 index c933fec..0000000 --- a/openapi.json +++ /dev/null @@ -1,1219 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "sero", - "description": "Muiltidomain static site hosting", - "contact": { - "name": "clowzed", - "email": "clowzed.work@gmail.com" - }, - "license": { - "name": "MIT" - }, - "version": "0.2.1" - }, - "paths": { - "/api/auth/login": { - "post": { - "tags": [ - "Account management" - ], - "summary": "Login user and receive JWT token.", - "description": "This endpoint allows users to login to sero server. The TTL for token is set by\nthe owner of the server by `JWT_TTL` env.", - "operationId": "Login", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LoginRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "User was successfully authenticated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LoginResponse" - } - } - } - }, - "400": { - "description": "Bad request or bad credentials. See details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Login was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Some error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - } - } - }, - "/api/auth/registration": { - "post": { - "tags": [ - "Account management" - ], - "summary": "Register new user for sero server.", - "description": "This endpoint creates new user for sero server. The amount of users is checked\nby [RegistrationGuard]. The amount of allowed users is determined by `MAX_USERS` env.", - "operationId": "Registration", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RegistrationRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "User was successfully registered.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RegistrationResponse" - } - } - } - }, - "400": { - "description": "Bad request or bad credentials. See details.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "409": { - "description": "Login has already been registered.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Some error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - } - } - }, - "/api/origin": { - "get": { - "tags": [ - "Origins Management and Dynamic Access Control" - ], - "summary": "List all origins for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", - "description": "This endpoint allows users to list all origins that are permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", - "operationId": "Get all origins", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "201": { - "description": "Origins were successfully retrieved for subdomain.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListOriginsResponse" - } - } - } - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - }, - "post": { - "tags": [ - "Origins Management and Dynamic Access Control" - ], - "summary": "Adds a new origin to a specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", - "description": "This endpoint allows users to add origins that are permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", - "operationId": "Create origin", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddOriginRequest" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "The origin was successfully added.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddOriginResponse" - } - } - } - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - }, - "delete": { - "tags": [ - "Origins Management and Dynamic Access Control" - ], - "summary": "Delete all origins for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", - "description": "This endpoint allows users to delete all origins that are permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", - "operationId": "Delete all origins", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Origins were successfully deleted for subdomain." - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - } - }, - "/api/origin/{id}": { - "get": { - "tags": [ - "Origins Management and Dynamic Access Control" - ], - "summary": "Get specified origin [by id] for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", - "description": "This endpoint allows users to get specified origin by id that is permitted to access resources\non specified subdomain. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", - "operationId": "Get origin by id", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "description": "Id of the origin to retrieve", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "200": { - "description": "Origin was successfully retrieved.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetOriginResponse" - } - } - } - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain or origin was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - }, - "delete": { - "tags": [ - "Origins Management and Dynamic Access Control" - ], - "summary": "Delete origin by id for specified subdomain for dynamic CORS (Cross-Origin Resource Sharing) management.", - "description": "This endpoint allows users to delete origin by id that is permitted to access resources\non their specified subdomains. The action is authenticated using a JWT, and the subdomain must\nbe owned by the user making the request. This will be checked by the server.", - "operationId": "Delete origin by id", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "'x-subdomain' header represents the name of the subdomain on which the action is to be performed.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "id", - "in": "path", - "description": "Id of the origin to delete", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], - "responses": { - "204": { - "description": "Origin was successfully deleted for subdomain." - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The origin is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain or origin was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - } - }, - "/api/site": { - "get": { - "tags": [ - "Actions" - ], - "summary": "Download site of the specified subdomain.", - "description": "Returns a zip file which was uploaded by user (last)", - "operationId": "Download site", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "x-subdomain header represents name of subdomain to call action on", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Site was successfully downloaded", - "content": { - "application/octet-stream": { - "schema": { - "type": "string" - } - } - } - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - }, - "post": { - "tags": [ - "Actions" - ], - "summary": "Uploads site for a specified subdomain.", - "description": "Warning: Old files will be removed after successful upload.\nThe cleanup task is configured with `CLEAN_OBSOLETE_INTERVAL` env\nIf upload fails then old files will be preserved.\nIf upload fails on th stage of extracting zips then\nnew subdomain will be associated with user\n\nUpload guard checks amount of uploads available for user.\nThe guard is configured with `MAX_SITES_PER_USER` env.", - "operationId": "Upload site", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "x-subdomain header represents name of subdomain to call action on", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/UploadData" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "Site was successfully uploaded" - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - }, - "delete": { - "tags": [ - "Actions" - ], - "summary": "Removes a specific site identified by the `x-subdomain` header.", - "description": "This endpoint allows authenticated users to remove a site associated with the specified subdomain.\nThe subdomain to be removed is specified in the `x-subdomain` header.", - "operationId": "Teardown site", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "x-subdomain header represents name of subdomain to call action on", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Site was successfully removed." - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - } - }, - "/api/site/disable": { - "patch": { - "tags": [ - "Actions" - ], - "summary": "Disables a specific site identified by the `x-subdomain` header.", - "description": "This endpoint allows authenticated users to disable a site associated with the specified subdomain.", - "operationId": "Disable site", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "x-subdomain header represents name of subdomain to call action on", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Site was successfully disabled." - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - } - }, - "/api/site/enable": { - "patch": { - "tags": [ - "Actions" - ], - "summary": "Enables a specific site identified by the `x-subdomain` header.", - "description": "This endpoint allows authenticated users to enable a site associated with the specified subdomain.", - "operationId": "Enable site", - "parameters": [ - { - "name": "x-subdomain", - "in": "header", - "description": "x-subdomain header represents name of subdomain to call action on", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Site was successfully enabled" - }, - "400": { - "description": "The 'x-subdomain' header is missing or contains invalid characters.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "401": { - "description": "Unauthorized: The JWT in the header is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "403": { - "description": "Forbidden: The subdomain is owned by another user.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "404": { - "description": "Not Found: The login or subdomain was not found. See details for more information.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - }, - "500": { - "description": "Internal Server Error: An error occurred on the server.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Details" - } - } - } - } - }, - "security": [ - { - "Bearer-JWT": [] - } - ] - } - } - }, - "components": { - "schemas": { - "AddOriginRequest": { - "type": "object", - "required": [ - "origin" - ], - "properties": { - "origin": { - "type": "string", - "description": "Origin to be added" - } - }, - "example": { - "origin": "https://example.com/" - } - }, - "AddOriginResponse": { - "type": "object", - "required": [ - "id", - "origin" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64", - "description": "Automatically generated id for new origin\nThis can be used for further management" - }, - "origin": { - "type": "string", - "description": "This duplicates origin from response payload\nto match REST specification" - } - }, - "example": { - "id": "42", - "origin": "https://example.com/" - } - }, - "Details": { - "type": "object", - "description": "This struct is a response of server in bad situation\nThat can be INTERNAL SERVER ERROR or BAD REQUEST\nYou can find all information in reason field", - "required": [ - "reason" - ], - "properties": { - "reason": { - "type": "string", - "description": "This field will contain error information" - } - } - }, - "GetOriginResponse": { - "type": "object", - "required": [ - "origin" - ], - "properties": { - "origin": { - "$ref": "#/components/schemas/OriginModel" - } - }, - "example": { - "origin": { - "id": 42, - "subdomain_id": 1, - "value": "https://example.com" - } - } - }, - "ListOriginsResponse": { - "type": "object", - "required": [ - "origins" - ], - "properties": { - "origins": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OriginModel" - }, - "description": "List of retrieved origins" - } - }, - "example": { - "origins": [ - { - "id": 42, - "subdomain_id": 1, - "value": "https://example.com" - } - ] - } - }, - "LoginRequest": { - "type": "object", - "required": [ - "login", - "password" - ], - "properties": { - "login": { - "type": "string", - "description": "The username used for authentication.\nIt must adhere to the following criteria:\n- It can contain letters (a-z), numbers (0-9), and periods (.).\n- It cannot contain any of the following characters: & = ' - + , < >\n- It cannot have multiple periods (.) consecutively.\n- Minimum length of 5 characters.\n- Maximum length of 40 characters.", - "maxLength": 40, - "minLength": 5 - }, - "password": { - "type": "string", - "description": "The password used for authentication.\nIt must meet the following requirements:\n- Minimum length of 12 characters.\n- Maximum length of 40 characters.\n- A combination of letters, numbers, and symbols.", - "maxLength": 40, - "minLength": 12 - } - } - }, - "LoginResponse": { - "type": "object", - "description": "The JWT token generated for authentication purposes.", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "string", - "description": "Token in JWT format" - } - }, - "example": { - "token": "ferwfwerfwer.fwerfwerfwerfwer.fwerfewfr" - } - }, - "OriginModel": { - "type": "object", - "required": [ - "id", - "subdomain_id", - "value" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "subdomain_id": { - "type": "integer", - "format": "int64" - }, - "value": { - "type": "string" - } - } - }, - "RegistrationRequest": { - "type": "object", - "required": [ - "login", - "password" - ], - "properties": { - "login": { - "type": "string", - "description": "The username used for authentication.\nIt must adhere to the following criteria:\n- It can contain letters (a-z), numbers (0-9), and periods (.).\n- It cannot contain any of the following characters: & = ' - + , < >\n- It cannot have multiple periods (.) consecutively.\n- Minimum length of 5 characters.\n- Maximum length of 40 characters.", - "maxLength": 40, - "minLength": 5 - }, - "password": { - "type": "string", - "description": "The password used for authentication.\nIt must meet the following requirements:\n- Minimum length of 12 characters.\n- Maximum length of 40 characters.\n- A combination of letters, numbers, and symbols.", - "maxLength": 40, - "minLength": 12 - } - } - }, - "RegistrationResponse": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64", - "description": "Auto generated id of a registered user" - } - }, - "example": { - "id": 1293983717 - } - }, - "UploadData": { - "type": "object", - "required": [ - "archive" - ], - "properties": { - "archive": { - "type": "string", - "format": "binary" - } - } - } - }, - "securitySchemes": { - "Bearer-JWT": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - } - } -} diff --git a/readme.md b/readme.md index 9c4ed94..744c7de 100644 --- a/readme.md +++ b/readme.md @@ -21,31 +21,23 @@

-# Warning - -> [!CAUTION] -> -> **_This project was in a huge rewrite and upload tool and docs are not updated! -> THis will be fixed very soon._** - ## 📖 Table Of Contents -- [Warning!](#warning) - - [📖 Table Of Contents](#-table-of-contents) - - [Docs](#docs) - - [🔧 Tools](#-tools) - - [❓ About The Project](#-about-the-project) - - [🚀 Features](#-features) - - [🔌 Built With](#-built-with) - - [📍 Roadmap](#-roadmap) - - [🧑‍🤝‍🧑 Contributing](#-contributing) - - [Creating A Pull Request](#creating-a-pull-request) - - [License](#license) - - [Authors](#authors) +- [📖 Table Of Contents](#-table-of-contents) +- [Docs](#docs) +- [🔧 Tools](#-tools) +- [❓ About The Project](#-about-the-project) +- [🚀 Features](#-features) +- [🔌 Built With](#-built-with) +- [📍 Roadmap](#-roadmap) +- [🧑‍🤝‍🧑 Contributing](#-contributing) + - [Creating A Pull Request](#creating-a-pull-request) +- [License](#license) +- [Authors](#authors) ## Docs -Read [docs here]("http://sero-docs.clowzed.ru") for fast installation. +Read [docs here]("clowzed.github.io/sero-docs/") for fast installation. ## 🔧 Tools @@ -70,7 +62,7 @@ One key feature that it is self-hosted. This gives users more flexibility and co - Custom 503.html `new` `(on disabled site)` - Clean urls - Dynamic CORS Management -- `[WIP]` Server events with websocket +- `[WIP]` SSE ## 🔌 Built With diff --git a/src/api/auth/login/error.rs b/src/api/auth/login/error.rs index 28fb312..e638c8a 100644 --- a/src/api/auth/login/error.rs +++ b/src/api/auth/login/error.rs @@ -27,7 +27,7 @@ impl IntoResponse for LoginError { fn into_response(self) -> Response { let reason = self.to_string(); let status_code: StatusCode = self.into(); - + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/auth/login/request.rs b/src/api/auth/login/request.rs index 3e8064e..ea7e6da 100644 --- a/src/api/auth/login/request.rs +++ b/src/api/auth/login/request.rs @@ -1,25 +1,15 @@ -use std::fmt::{self, Debug}; - use serde::{Deserialize, Serialize}; +use std::fmt::{self, Debug}; use utoipa::{schema, ToSchema}; -use validator::{Validate, ValidationError}; +use validator::Validate; #[derive(Deserialize, Serialize, Validate, ToSchema)] pub struct LoginRequest { /// The username used for authentication. /// It must adhere to the following criteria: - /// - It can contain letters (a-z), numbers (0-9), and periods (.). - /// - It cannot contain any of the following characters: & = ' - + , < > - /// - It cannot have multiple periods (.) consecutively. /// - Minimum length of 5 characters. /// - Maximum length of 40 characters. - #[validate( - length(min = 5, max = 40), - custom( - function = validate_login, - message = "Login can contain letters (a-z), numbers (0-9), and periods (.), - and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)" - ))] + #[validate(length(min = 5, max = 40))] #[schema(min_length = 5, max_length = 40)] pub login: String, @@ -27,53 +17,11 @@ pub struct LoginRequest { /// It must meet the following requirements: /// - Minimum length of 12 characters. /// - Maximum length of 40 characters. - /// - A combination of letters, numbers, and symbols. - #[validate( - length(min = 12, max = 40), - custom(function = validate_password, - message = "Minimum length of 12 characters and maximum length of 40 characters and a combination of - letters, numbers, and symbols.") - )] + #[validate(length(min = 12, max = 40))] #[schema(min_length = 12, max_length = 40)] pub password: String, } -fn validate_login(login: &str) -> Result<(), ValidationError> { - let invalid_chars = "&='+,<>"; - let invalid_double_period = ".."; - - if login.chars().any(|c| invalid_chars.contains(c)) || login.contains(invalid_double_period) { - return Err(ValidationError::new( - "Rules for login: Login can contain letters (a-z), numbers (0-9), and periods (.), - and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)", - )); - } - - Ok(()) -} - -fn validate_password(password: &str) -> Result<(), ValidationError> { - let mut has_digit = false; - let mut has_special_char = false; - - for c in password.chars() { - if !has_digit && c.is_ascii_digit() { - has_digit = true; - } else if c.is_ascii_punctuation() || c.is_ascii_whitespace() { - has_special_char = true; - } - - if has_digit && has_special_char { - return Ok(()); - } - } - - Err(ValidationError::new( - "Rules for password: Minimum length of 12 characters and maximum length of 40 characters and a combination of - letters, numbers, and symbols.", - )) -} - impl Debug for LoginRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LoginRequest") diff --git a/src/api/auth/registration/error.rs b/src/api/auth/registration/error.rs index 4518139..f7d5ad9 100644 --- a/src/api/auth/registration/error.rs +++ b/src/api/auth/registration/error.rs @@ -32,6 +32,7 @@ impl IntoResponse for RegistrationError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/auth/registration/request.rs b/src/api/auth/registration/request.rs index a5ed8f2..e63ea53 100644 --- a/src/api/auth/registration/request.rs +++ b/src/api/auth/registration/request.rs @@ -1,25 +1,15 @@ -use std::fmt::{self, Debug}; - use serde::{Deserialize, Serialize}; +use std::fmt::{self, Debug}; use utoipa::{schema, ToSchema}; -use validator::{Validate, ValidationError}; +use validator::Validate; #[derive(Deserialize, Serialize, Validate, ToSchema)] pub struct RegistrationRequest { /// The username used for authentication. /// It must adhere to the following criteria: - /// - It can contain letters (a-z), numbers (0-9), and periods (.). - /// - It cannot contain any of the following characters: & = ' - + , < > - /// - It cannot have multiple periods (.) consecutively. /// - Minimum length of 5 characters. /// - Maximum length of 40 characters. - #[validate( - length(min = 5, max = 40), - custom( - function = validate_login, - message = "Login can contain letters (a-z), numbers (0-9), and periods (.), - and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)" - ))] + #[validate(length(min = 5, max = 40))] #[schema(min_length = 5, max_length = 40)] pub login: String, @@ -27,53 +17,11 @@ pub struct RegistrationRequest { /// It must meet the following requirements: /// - Minimum length of 12 characters. /// - Maximum length of 40 characters. - /// - A combination of letters, numbers, and symbols. - #[validate( - length(min = 12, max = 40), - custom(function = validate_password, - message = "Minimum length of 12 characters and maximum length of 40 characters and a combination of - letters, numbers, and symbols.") - )] + #[validate(length(min = 12, max = 40))] #[schema(min_length = 12, max_length = 40)] pub password: String, } -fn validate_login(login: &str) -> Result<(), ValidationError> { - let invalid_chars = "&='+,<>"; - let invalid_double_period = ".."; - - if login.chars().any(|c| invalid_chars.contains(c)) || login.contains(invalid_double_period) { - return Err(ValidationError::new( - "Rules for login: Login can contain letters (a-z), numbers (0-9), and periods (.), - and cannot contain any of the following characters: & = ' + , < > or multiple periods (.)", - )); - } - - Ok(()) -} - -fn validate_password(password: &str) -> Result<(), ValidationError> { - let mut has_digit = false; - let mut has_special_char = false; - - for c in password.chars() { - if !has_digit && c.is_ascii_digit() { - has_digit = true; - } else if c.is_ascii_punctuation() || c.is_ascii_whitespace() { - has_special_char = true; - } - - if has_digit && has_special_char { - return Ok(()); - } - } - - Err(ValidationError::new( - "Rules for password: Minimum length of 12 characters and maximum length of 40 characters and a combination of - letters, numbers, and symbols.", - )) -} - impl Debug for RegistrationRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RegistrationRequest") diff --git a/src/api/origin/create/error.rs b/src/api/origin/create/error.rs index 4c1654c..8d4ace7 100644 --- a/src/api/origin/create/error.rs +++ b/src/api/origin/create/error.rs @@ -24,6 +24,7 @@ impl IntoResponse for AddOriginError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/delete/error.rs b/src/api/origin/delete/error.rs index e436ff3..24b1a7d 100644 --- a/src/api/origin/delete/error.rs +++ b/src/api/origin/delete/error.rs @@ -28,6 +28,7 @@ impl IntoResponse for DeleteOriginError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/list/error.rs b/src/api/origin/list/error.rs index 8aedb45..850bf8b 100644 --- a/src/api/origin/list/error.rs +++ b/src/api/origin/list/error.rs @@ -24,6 +24,7 @@ impl IntoResponse for ListOriginsError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/purge/error.rs b/src/api/origin/purge/error.rs index 6bd6f62..3d92d06 100644 --- a/src/api/origin/purge/error.rs +++ b/src/api/origin/purge/error.rs @@ -24,6 +24,7 @@ impl IntoResponse for DeleteOriginsError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/origin/retrieve/error.rs b/src/api/origin/retrieve/error.rs index 0a5d730..b807ef4 100644 --- a/src/api/origin/retrieve/error.rs +++ b/src/api/origin/retrieve/error.rs @@ -24,6 +24,7 @@ impl IntoResponse for GetOriginError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/disable/error.rs b/src/api/site/disable/error.rs index 058cd28..00b4d3b 100644 --- a/src/api/site/disable/error.rs +++ b/src/api/site/disable/error.rs @@ -27,6 +27,7 @@ impl IntoResponse for DisableError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/download/error.rs b/src/api/site/download/error.rs index 2acb2d6..68c03d2 100644 --- a/src/api/site/download/error.rs +++ b/src/api/site/download/error.rs @@ -32,6 +32,7 @@ impl IntoResponse for DownloadError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/enable/error.rs b/src/api/site/enable/error.rs index 4c32ed4..76b3f9d 100644 --- a/src/api/site/enable/error.rs +++ b/src/api/site/enable/error.rs @@ -28,6 +28,7 @@ impl IntoResponse for EnableError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/page/error.rs b/src/api/site/page/error.rs index e2dfa3e..9c491cf 100644 --- a/src/api/site/page/error.rs +++ b/src/api/site/page/error.rs @@ -28,6 +28,7 @@ impl IntoResponse for PageError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/teardown/error.rs b/src/api/site/teardown/error.rs index c754e8a..d55e5a3 100644 --- a/src/api/site/teardown/error.rs +++ b/src/api/site/teardown/error.rs @@ -28,6 +28,7 @@ impl IntoResponse for TeardownError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/api/site/upload/error.rs b/src/api/site/upload/error.rs index 62d9273..8feb853 100644 --- a/src/api/site/upload/error.rs +++ b/src/api/site/upload/error.rs @@ -34,6 +34,7 @@ impl IntoResponse for UploadError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/auth.rs b/src/extractors/auth.rs index 79d97c3..15184d0 100644 --- a/src/extractors/auth.rs +++ b/src/extractors/auth.rs @@ -61,6 +61,7 @@ impl IntoResponse for AuthError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/guards/upload.rs b/src/extractors/guards/upload.rs index b2dee44..364d8e1 100644 --- a/src/extractors/guards/upload.rs +++ b/src/extractors/guards/upload.rs @@ -49,6 +49,7 @@ impl IntoResponse for GuardError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/subdomain.rs b/src/extractors/subdomain.rs index 917cd2c..780876e 100644 --- a/src/extractors/subdomain.rs +++ b/src/extractors/subdomain.rs @@ -41,6 +41,7 @@ impl IntoResponse for SubdomainError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/subdomain_name.rs b/src/extractors/subdomain_name.rs index b3621c2..b17324d 100644 --- a/src/extractors/subdomain_name.rs +++ b/src/extractors/subdomain_name.rs @@ -32,6 +32,7 @@ impl IntoResponse for SubdomainNameError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/extractors/subdomain_owned.rs b/src/extractors/subdomain_owned.rs index f6889b8..f55d951 100644 --- a/src/extractors/subdomain_owned.rs +++ b/src/extractors/subdomain_owned.rs @@ -41,6 +41,7 @@ impl IntoResponse for SubdomainOwnedError { let reason = self.to_string(); let status_code: StatusCode = self.into(); + tracing::error!(%reason, %status_code, "Error occurred while trying to handle request!"); (status_code, Json(Details { reason })).into_response() } } diff --git a/src/lib.rs b/src/lib.rs index f58974a..3120d51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ use configuration::{reader::ConfigurationReader, *}; use futures::StreamExt; use migration::{Migrator, MigratorTrait}; use origin::service::Service as CorsService; -use sea_orm::{ConnectOptions, Database, DbErr}; +use sea_orm::{ActiveModelTrait, ConnectOptions, Database, DbErr, IntoActiveModel}; use serde::{Deserialize, Serialize}; use services::*; use site::service::Service as SiteService; @@ -165,6 +165,15 @@ pub async fn app() -> Result<(Router, Arc), AppCreationError> { |cause| tracing::warn!(%cause, "Failed to remove file with path : {}", file.real_path), ) .ok(); + + let file_id = file.id; + file.into_active_model() + .delete(state_for_file_deletion_task.connection()) + .await + .inspect_err( + |cause| tracing::warn!(%cause, %file_id, "Failed to remove file from database"), + ) + .ok(); } } } @@ -197,6 +206,12 @@ pub async fn app() -> Result<(Router, Arc), AppCreationError> { }; async move { + //? If header was not provided + //? Allow as it probably management tool + if task.subdomain.is_empty() { + return true; + } + if cors_task_sender .send(task) .await @@ -232,10 +247,10 @@ pub async fn app() -> Result<(Router, Arc), AppCreationError> { let mut app = Router::new() .merge(openapi) - .nest("/api", api::router()) .route("/*path", get(api::site::page::handler::implementation)) .route("/", get(api::site::page::handler::redirect::implementation)) .layer(cors_layer) + .nest("/api", api::router()) .layer(tracing_layer) .layer(TimeoutLayer::new(Duration::from_secs(10))) .with_state(state.clone()); diff --git a/src/services/archive/service.rs b/src/services/archive/service.rs index da255f1..70c8aa0 100644 --- a/src/services/archive/service.rs +++ b/src/services/archive/service.rs @@ -44,11 +44,11 @@ impl Service { .filename() .as_str() .inspect_err(|cause| tracing::warn!(%cause, "Failed to convert entry filepath to str"))?; - PathBuf::from(entry_filename).components().skip(1).collect::() + PathBuf::from(entry_filename) }; tracing::trace!(?path, "Entry filepath was successfully retrieved"); - //? Generating filename for entry + //? Generating filename for enty // Just random to prevent collisions let u1 = Uuid::new_v4(); let u2 = Uuid::new_v4();