mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-06 17:57:47 +03:00
Compare commits
11 Commits
85616c5503
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
fceffb900d
|
|||
|
49ce0005dc
|
|||
|
4c738085f2
|
|||
|
20602eb863
|
|||
|
e04d462223
|
|||
|
22af02464d
|
|||
|
9a517519db
|
|||
|
65376e75f7
|
|||
|
bef6163c1b
|
|||
|
283858fea3
|
|||
|
66ad4ef938
|
169
.github/workflows/release.yml
vendored
Normal file
169
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ "release/v*" ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
BINARY_NAME: schedule-parser-rusted
|
||||
|
||||
TEST_DB: ${{ secrets.TEST_DATABASE_URL }}
|
||||
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
|
||||
DOCKER_IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
DOCKER_REGISTRY_HOST: registry.n08i40k.ru
|
||||
DOCKER_REGISTRY_USERNAME: ${{ github.repository_owner }}
|
||||
DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
touch .env.test
|
||||
cargo test --verbose
|
||||
env:
|
||||
DATABASE_URL: ${{ env.TEST_DB }}
|
||||
JWT_SECRET: "test-secret-at-least-256-bits-used"
|
||||
VKID_CLIENT_ID: 0
|
||||
VKID_REDIRECT_URI: "vk0://vk.com/blank.html"
|
||||
REQWEST_USER_AGENT: "Dalvik/2.1.0 (Linux; U; Android 6.0.1; OPPO R9s Build/MMB29M)"
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.11.0
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --verbose
|
||||
|
||||
- name: Extract debug symbols
|
||||
run: |
|
||||
objcopy --only-keep-debug target/release/${{ env.BINARY_NAME }}{,.d}
|
||||
objcopy --strip-debug --strip-unneeded target/release/${{ env.BINARY_NAME }}
|
||||
objcopy --add-gnu-debuglink target/release/${{ env.BINARY_NAME }}{.d,}
|
||||
|
||||
- name: Setup sentry-cli
|
||||
uses: matbour/setup-sentry-cli@v2.0.0
|
||||
with:
|
||||
version: latest
|
||||
token: ${{ env.SENTRY_AUTH_TOKEN }}
|
||||
organization: ${{ env.SENTRY_ORG }}
|
||||
project: ${{ env.SENTRY_PROJECT }}
|
||||
|
||||
- name: Upload debug symbols to Sentry
|
||||
run: |
|
||||
sentry-cli debug-files upload --include-sources .
|
||||
|
||||
- name: Upload build binary artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-binary
|
||||
path: target/release/${{ env.BINARY_NAME }}
|
||||
|
||||
- name: Upload build debug symbols artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-symbols
|
||||
path: target/release/${{ env.BINARY_NAME }}.d
|
||||
|
||||
docker:
|
||||
name: Build & Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-binary
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
|
||||
- name: Login to Registry
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
registry: ${{ env.DOCKER_REGISTRY_HOST }}
|
||||
username: ${{ env.DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ env.DOCKER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.7.0
|
||||
with:
|
||||
images: ${{ env.DOCKER_REGISTRY_HOST }}/${{ env.DOCKER_IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
"BINARY_NAME=${{ env.BINARY_NAME }}"
|
||||
release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- docker
|
||||
# noinspection GrazieInspection,SpellCheckingInspection
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate changelog
|
||||
run: |
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 HEAD^)
|
||||
echo "## Коммиты с прошлого релиза $LAST_TAG" > CHANGELOG.md
|
||||
git log $LAST_TAG..HEAD --oneline >> CHANGELOG.md
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1.16.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
artifacts: "${{ env.BINARY_NAME }},${{ env.BINARY_NAME }}.d"
|
||||
bodyFile: CHANGELOG.md
|
||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -1,10 +1,9 @@
|
||||
name: Tests
|
||||
name: cargo test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
tags-ignore: [ "release/v*" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -29,4 +28,5 @@ jobs:
|
||||
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
||||
JWT_SECRET: "test-secret-at-least-256-bits-used"
|
||||
VKID_CLIENT_ID: 0
|
||||
VKID_REDIRECT_URI: "vk0://vk.com/blank.html"
|
||||
VKID_REDIRECT_URI: "vk0://vk.com/blank.html"
|
||||
REQWEST_USER_AGENT: "Dalvik/2.1.0 (Linux; U; Android 6.0.1; OPPO R9s Build/MMB29M)"
|
||||
9
.idea/sqldialects.xml
generated
9
.idea/sqldialects.xml
generated
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/migrations/2025-03-21-211822_create_user_role/down.sql" dialect="PostgreSQL" />
|
||||
<file url="file://$PROJECT_DIR$/migrations/2025-03-21-212111_create_users/up.sql" dialect="PostgreSQL" />
|
||||
<file url="file://$PROJECT_DIR$/migrations/2025-03-21-212723_create_fcm/down.sql" dialect="PostgreSQL" />
|
||||
<file url="file://$PROJECT_DIR$/migrations/2025-03-21-212723_create_fcm/up.sql" dialect="PostgreSQL" />
|
||||
</component>
|
||||
</project>
|
||||
271
Cargo.lock
generated
271
Cargo.lock
generated
@@ -872,6 +872,16 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
@@ -1039,9 +1049,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.8"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1072,10 +1082,22 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "findshlibs"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"lazy_static 1.5.0",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "firebase-messaging-rs"
|
||||
version = "0.8.10"
|
||||
source = "git+ssh://git@github.com/i10416/firebase-messaging-rs.git#f2cb78b0bda33a41962a1a2ed178fb9c5be59e6a"
|
||||
source = "git+https://github.com/i10416/firebase-messaging-rs.git#f2cb78b0bda33a41962a1a2ed178fb9c5be59e6a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
@@ -1404,6 +1426,17 @@ dependencies = [
|
||||
"winutil",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
@@ -1917,6 +1950,12 @@ version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
@@ -2090,8 +2129,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a605e0778d73324c897e7e4bd5903f64639884a99d1bad55bccfd986260063"
|
||||
dependencies = [
|
||||
"byteorder 0.3.13",
|
||||
"hostname",
|
||||
"lazy_static",
|
||||
"hostname 0.1.5",
|
||||
"lazy_static 0.2.11",
|
||||
"libc",
|
||||
"quick-error",
|
||||
"rand 0.3.23",
|
||||
@@ -2155,6 +2194,17 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@@ -2391,7 +2441,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -2411,7 +2461,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -2627,6 +2677,7 @@ dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.8",
|
||||
@@ -2715,6 +2766,15 @@ version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.3"
|
||||
@@ -2816,7 +2876,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schedule-parser-rusted"
|
||||
version = "0.8.0"
|
||||
version = "1.0.3"
|
||||
dependencies = [
|
||||
"actix-macros 0.1.0",
|
||||
"actix-test",
|
||||
@@ -2840,6 +2900,8 @@ dependencies = [
|
||||
"rand 0.9.0",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"sentry",
|
||||
"sentry-actix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -2907,6 +2969,133 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "sentry"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "255914a8e53822abd946e2ce8baa41d4cded6b8e938913b7f7b9da5b7ab44335"
|
||||
dependencies = [
|
||||
"httpdate",
|
||||
"native-tls",
|
||||
"reqwest",
|
||||
"sentry-backtrace",
|
||||
"sentry-contexts",
|
||||
"sentry-core",
|
||||
"sentry-debug-images",
|
||||
"sentry-panic",
|
||||
"sentry-tracing",
|
||||
"tokio",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-actix"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a927aed43cce0e9240f7477ac81cdfa2ffb048e0e2b17000eb5976e14f063993"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-web",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-backtrace"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00293cd332a859961f24fd69258f7e92af736feaeb91020cff84dac4188a4302"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-contexts"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "961990f9caa76476c481de130ada05614cd7f5aa70fb57c2142f0e09ad3fb2aa"
|
||||
dependencies = [
|
||||
"hostname 0.4.1",
|
||||
"libc",
|
||||
"os_info",
|
||||
"rustc_version",
|
||||
"sentry-core",
|
||||
"uname",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-core"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a6409d845707d82415c800290a5d63be5e3df3c2e417b0997c60531dfbd35ef"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"sentry-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-debug-images"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ab5df4f3b64760508edfe0ba4290feab5acbbda7566a79d72673065888e5cc"
|
||||
dependencies = [
|
||||
"findshlibs",
|
||||
"once_cell",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-panic"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "609b1a12340495ce17baeec9e08ff8ed423c337c1a84dffae36a178c783623f3"
|
||||
dependencies = [
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-tracing"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f4e86402d5c50239dc7d8fd3f6d5e048221d5fcb4e026d8d50ab57fe4644cb"
|
||||
dependencies = [
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-types"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3f117b8755dbede8260952de2aeb029e20f432e72634e8969af34324591631"
|
||||
dependencies = [
|
||||
"debugid",
|
||||
"hex",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"time 0.3.40",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@@ -3043,7 +3232,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
"time 0.3.40",
|
||||
]
|
||||
|
||||
@@ -3166,13 +3355,33 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 2.0.12",
|
||||
]
|
||||
|
||||
[[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.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3468,6 +3677,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3482,6 +3701,15 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "uname"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.1"
|
||||
@@ -3506,6 +3734,19 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
@@ -3515,6 +3756,7 @@ dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3589,8 +3831,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -4118,7 +4367,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"indexmap 2.8.0",
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"thiserror 2.0.12",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -3,10 +3,13 @@ members = ["actix-macros", "actix-test"]
|
||||
|
||||
[package]
|
||||
name = "schedule-parser-rusted"
|
||||
version = "0.8.0"
|
||||
version = "1.0.3"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.10.2"
|
||||
actix-macros = { path = "actix-macros" }
|
||||
@@ -17,8 +20,8 @@ derive_more = "2.0.1"
|
||||
diesel = { version = "2.2.8", features = ["postgres"] }
|
||||
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
||||
dotenvy = "0.15.7"
|
||||
env_logger = "0.11.8"
|
||||
firebase-messaging-rs = { git = "ssh://git@github.com/i10416/firebase-messaging-rs.git" }
|
||||
env_logger = "0.11.7"
|
||||
firebase-messaging-rs = { git = "https://github.com/i10416/firebase-messaging-rs.git" }
|
||||
futures-util = "0.3.31"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
|
||||
@@ -27,6 +30,8 @@ mime = "0.3.17"
|
||||
objectid = "0.2.0"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.15", features = ["json"] }
|
||||
sentry = "0.37.0"
|
||||
sentry-actix = "0.37.0"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
serde_with = "3.12.0"
|
||||
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM debian:stable-slim
|
||||
LABEL authors="n08i40k"
|
||||
|
||||
ARG BINARY_NAME
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN apt update && \
|
||||
apt install -y libpq5 ca-certificates openssl
|
||||
|
||||
COPY ./${BINARY_NAME} /bin/main
|
||||
RUN chmod +x /bin/main
|
||||
|
||||
ENTRYPOINT ["main"]
|
||||
40
src/main.rs
40
src/main.rs
@@ -4,6 +4,7 @@ use crate::middlewares::content_type::ContentTypeBootstrap;
|
||||
use actix_web::dev::{ServiceFactory, ServiceRequest};
|
||||
use actix_web::{App, Error, HttpServer};
|
||||
use dotenvy::dotenv;
|
||||
use std::io;
|
||||
use utoipa_actix_web::AppExt;
|
||||
use utoipa_actix_web::scope::Scope;
|
||||
use utoipa_rapidoc::RapiDoc;
|
||||
@@ -69,12 +70,8 @@ pub fn get_api_scope<
|
||||
.service(vk_id_scope)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
|
||||
unsafe { std::env::set_var("RUST_LOG", "debug") };
|
||||
env_logger::init();
|
||||
async fn async_main() -> io::Result<()> {
|
||||
println!("Starting server...");
|
||||
|
||||
let app_state = app_state().await;
|
||||
|
||||
@@ -82,7 +79,11 @@ async fn main() {
|
||||
let (app, api) = App::new()
|
||||
.into_utoipa_app()
|
||||
.app_data(app_state.clone())
|
||||
.service(get_api_scope("/api/v1").wrap(ContentTypeBootstrap))
|
||||
.service(
|
||||
get_api_scope("/api/v1")
|
||||
.wrap(sentry_actix::Sentry::new())
|
||||
.wrap(ContentTypeBootstrap),
|
||||
)
|
||||
.split_for_parts();
|
||||
|
||||
let rapidoc_service = RapiDoc::with_openapi("/api-docs-json", api).path("/api-docs");
|
||||
@@ -96,9 +97,28 @@ async fn main() {
|
||||
app.service(rapidoc_service.custom_html(patched_rapidoc_html))
|
||||
})
|
||||
.workers(4)
|
||||
.bind(("0.0.0.0", 5050))
|
||||
.unwrap()
|
||||
.bind(("0.0.0.0", 5050))?
|
||||
.run()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let _guard = sentry::init((
|
||||
"https://9c33db76e89984b3f009b28a9f4b5954@sentry.n08i40k.ru/8",
|
||||
sentry::ClientOptions {
|
||||
release: sentry::release_name!(),
|
||||
send_default_pii: true,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
|
||||
unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
|
||||
|
||||
dotenv().unwrap();
|
||||
|
||||
env_logger::init();
|
||||
|
||||
actix_web::rt::System::new().block_on(async { async_main().await })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::parser::LessonParseResult::{Lessons, Street};
|
||||
use crate::parser::schema::LessonType::Break;
|
||||
use crate::parser::schema::{
|
||||
Day, Lesson, LessonSubGroup, LessonTime, LessonType, ParseError, ParseResult, ScheduleEntry,
|
||||
Day, ErrorCell, ErrorCellPos, Lesson, LessonSubGroup, LessonTime, LessonType, ParseError,
|
||||
ParseResult, ScheduleEntry,
|
||||
};
|
||||
use crate::parser::LessonParseResult::{Lessons, Street};
|
||||
use calamine::{open_workbook_from_rs, Reader, Xls};
|
||||
use calamine::{Reader, Xls, open_workbook_from_rs};
|
||||
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
@@ -56,9 +57,8 @@ fn get_string_from_cell(worksheet: &WorkSheet, row: u32, col: u32) -> Option<Str
|
||||
return None;
|
||||
}
|
||||
|
||||
static NL_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[\n\r]+").unwrap());
|
||||
static SP_RE: LazyLock<Regex, fn() -> Regex> = LazyLock::new(|| Regex::new(r"\s+").unwrap());
|
||||
static NL_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[\n\r]+").unwrap());
|
||||
static SP_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\s+").unwrap());
|
||||
|
||||
let trimmed_data = SP_RE
|
||||
.replace_all(&NL_RE.replace_all(&cell_data, " "), " ")
|
||||
@@ -252,7 +252,7 @@ fn parse_lesson(
|
||||
|
||||
let raw_name = raw_name_opt.unwrap();
|
||||
|
||||
static OTHER_STREET_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
static OTHER_STREET_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^[А-Я][а-я]+,?\s?[0-9]+$").unwrap());
|
||||
|
||||
if OTHER_STREET_RE.is_match(&raw_name) {
|
||||
@@ -275,7 +275,9 @@ fn parse_lesson(
|
||||
.filter(|time| time.xls_range.1.0 == cell_range.1.0)
|
||||
.collect::<Vec<&InternalTime>>();
|
||||
|
||||
let end_time = end_time_arr.first().ok_or(ParseError::LessonTimeNotFound)?;
|
||||
let end_time = end_time_arr
|
||||
.first()
|
||||
.ok_or(ParseError::LessonTimeNotFound(ErrorCellPos { row, column }))?;
|
||||
|
||||
let range: Option<[u8; 2]> = if time.default_index != None {
|
||||
let default = time.default_index.unwrap() as u8;
|
||||
@@ -389,14 +391,12 @@ fn parse_cabinets(worksheet: &WorkSheet, row: u32, column: u32) -> Vec<String> {
|
||||
|
||||
/// Getting the "pure" name of the lesson and list of teachers from the text of the lesson cell.
|
||||
fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup>), ParseError> {
|
||||
static LESSON_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
static LESSON_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?:[А-Я][а-я]+[А-Я]{2}(?:\([0-9][а-я]+\))?)+$").unwrap());
|
||||
static TEACHER_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
static TEACHER_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"([А-Я][а-я]+)([А-Я])([А-Я])(?:\(([0-9])[а-я]+\))?").unwrap());
|
||||
static CLEAN_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[\s.,]+").unwrap());
|
||||
static END_CLEAN_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[.\s]+$").unwrap());
|
||||
static CLEAN_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[\s.,]+").unwrap());
|
||||
static END_CLEAN_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[.\s]+$").unwrap());
|
||||
|
||||
let (teachers, lesson_name) = {
|
||||
let clean_name = CLEAN_RE.replace_all(&name, "").to_string();
|
||||
@@ -423,14 +423,9 @@ fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup
|
||||
|
||||
for captures in teacher_it {
|
||||
subgroups.push(LessonSubGroup {
|
||||
number: if let Some(capture) = captures.get(4) {
|
||||
capture
|
||||
.as_str()
|
||||
.to_string()
|
||||
.parse::<u8>()
|
||||
.map_err(|_| ParseError::SubgroupIndexParsingFailed)?
|
||||
} else {
|
||||
0
|
||||
number: match captures.get(4) {
|
||||
Some(capture) => capture.as_str().to_string().parse::<u8>().unwrap(),
|
||||
None => 0,
|
||||
},
|
||||
cabinet: None,
|
||||
teacher: format!(
|
||||
@@ -665,10 +660,12 @@ pub fn parse_xls(buffer: &Vec<u8>) -> Result<ParseResult, ParseError> {
|
||||
|
||||
// time
|
||||
let time_range = {
|
||||
static TIME_RE: LazyLock<Regex, fn() -> Regex> =
|
||||
static TIME_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(\d+\.\d+)-(\d+\.\d+)").unwrap());
|
||||
|
||||
let parse_res = TIME_RE.captures(&time).ok_or(ParseError::GlobalTime)?;
|
||||
let parse_res = TIME_RE.captures(&time).ok_or(ParseError::GlobalTime(
|
||||
ErrorCell::new(row, lesson_time_column, time.clone()),
|
||||
))?;
|
||||
|
||||
let start_match = parse_res.get(1).unwrap().as_str();
|
||||
let start_parts: Vec<&str> = start_match.split(".").collect();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::Display;
|
||||
use derive_more::{Display, Error};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::collections::HashMap;
|
||||
@@ -115,10 +115,33 @@ pub struct ParseResult {
|
||||
pub teachers: HashMap<String, ScheduleEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Clone, ToSchema)]
|
||||
#[derive(Clone, Debug, Display, Error, ToSchema)]
|
||||
#[display("row {row}, column {column}")]
|
||||
pub struct ErrorCellPos {
|
||||
pub row: u32,
|
||||
pub column: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Display, Error, ToSchema)]
|
||||
#[display("'{data}' at {pos}")]
|
||||
pub struct ErrorCell {
|
||||
pub pos: ErrorCellPos,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
impl ErrorCell {
|
||||
pub fn new(row: u32, column: u32, data: String) -> Self {
|
||||
Self {
|
||||
pos: ErrorCellPos { row, column },
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Display, Error, ToSchema)]
|
||||
pub enum ParseError {
|
||||
/// Errors related to reading XLS file.
|
||||
#[display("{}: Failed to read XLS file.", "_0")]
|
||||
#[display("{_0:?}: Failed to read XLS file.")]
|
||||
#[schema(value_type = String)]
|
||||
BadXLS(Arc<calamine::XlsError>),
|
||||
|
||||
@@ -131,16 +154,12 @@ pub enum ParseError {
|
||||
UnknownWorkSheetRange,
|
||||
|
||||
/// Failed to read the beginning and end of the lesson from the line
|
||||
#[display("Failed to read lesson start and end times from string.")]
|
||||
GlobalTime,
|
||||
#[display("Failed to read lesson start and end times from {_0}.")]
|
||||
GlobalTime(ErrorCell),
|
||||
|
||||
/// Not found the beginning and the end corresponding to the lesson.
|
||||
#[display("No start and end times matching the lesson was found.")]
|
||||
LessonTimeNotFound,
|
||||
|
||||
/// Failed to read the subgroup index.
|
||||
#[display("Failed to read subgroup index.")]
|
||||
SubgroupIndexParsingFailed,
|
||||
#[display("No start and end times matching the lesson (at {_0}) was found.")]
|
||||
LessonTimeNotFound(ErrorCellPos),
|
||||
}
|
||||
|
||||
impl Serialize for ParseError {
|
||||
@@ -154,11 +173,8 @@ impl Serialize for ParseError {
|
||||
ParseError::UnknownWorkSheetRange => {
|
||||
serializer.serialize_str("UNKNOWN_WORK_SHEET_RANGE")
|
||||
}
|
||||
ParseError::GlobalTime => serializer.serialize_str("GLOBAL_TIME"),
|
||||
ParseError::LessonTimeNotFound => serializer.serialize_str("LESSON_TIME_NOT_FOUND"),
|
||||
ParseError::SubgroupIndexParsingFailed => {
|
||||
serializer.serialize_str("SUBGROUP_INDEX_PARSING_FAILED")
|
||||
}
|
||||
ParseError::GlobalTime(_) => serializer.serialize_str("GLOBAL_TIME"),
|
||||
ParseError::LessonTimeNotFound(_) => serializer.serialize_str("LESSON_TIME_NOT_FOUND"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::utility::jwt::DEFAULT_ALGORITHM;
|
||||
use jsonwebtoken::errors::ErrorKind;
|
||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct TokenData {
|
||||
@@ -17,7 +14,7 @@ struct TokenData {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
sub: i32,
|
||||
iis: String,
|
||||
jti: i32,
|
||||
app: i32,
|
||||
@@ -52,17 +49,10 @@ const VK_PUBLIC_KEY: &str = concat!(
|
||||
"-----END PUBLIC KEY-----"
|
||||
);
|
||||
|
||||
static VK_ID_CLIENT_ID: LazyLock<i32> = LazyLock::new(|| {
|
||||
env::var("VK_ID_CLIENT_ID")
|
||||
.expect("VK_ID_CLIENT_ID must be set")
|
||||
.parse::<i32>()
|
||||
.expect("VK_ID_CLIENT_ID must be i32")
|
||||
});
|
||||
|
||||
pub fn parse_vk_id(token_str: &String) -> Result<i32, Error> {
|
||||
pub fn parse_vk_id(token_str: &String, client_id: i32) -> Result<i32, Error> {
|
||||
let dkey = DecodingKey::from_rsa_pem(VK_PUBLIC_KEY.as_bytes()).unwrap();
|
||||
|
||||
match decode::<Claims>(&token_str, &dkey, &Validation::new(DEFAULT_ALGORITHM)) {
|
||||
match decode::<Claims>(&token_str, &dkey, &Validation::new(Algorithm::RS256)) {
|
||||
Ok(token_data) => {
|
||||
let claims = token_data.claims;
|
||||
|
||||
@@ -70,13 +60,10 @@ pub fn parse_vk_id(token_str: &String) -> Result<i32, Error> {
|
||||
Err(Error::UnknownIssuer(claims.iis))
|
||||
} else if claims.jti != 21 {
|
||||
Err(Error::UnknownType(claims.jti))
|
||||
} else if claims.app != *VK_ID_CLIENT_ID {
|
||||
} else if claims.app != client_id {
|
||||
Err(Error::UnknownClientId(claims.app))
|
||||
} else {
|
||||
match claims.sub.parse::<i32>() {
|
||||
Ok(sub) => Ok(sub),
|
||||
Err(_) => Err(Error::InvalidToken),
|
||||
}
|
||||
Ok(claims.sub)
|
||||
}
|
||||
}
|
||||
Err(err) => Err(match err.into_kind() {
|
||||
|
||||
@@ -71,7 +71,7 @@ pub async fn sign_in_vk(
|
||||
) -> ServiceResponse {
|
||||
let data = data_json.into_inner();
|
||||
|
||||
match parse_vk_id(&data.access_token) {
|
||||
match parse_vk_id(&data.access_token, app_state.vk_id.client_id) {
|
||||
Ok(id) => sign_in_combined(Vk(id), &app_state).await.into(),
|
||||
Err(_) => ErrorCode::InvalidVkAccessToken.into_response(),
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ pub async fn sign_up_vk(
|
||||
) -> ServiceResponse {
|
||||
let data = data_json.into_inner();
|
||||
|
||||
match parse_vk_id(&data.access_token) {
|
||||
match parse_vk_id(&data.access_token, app_state.vk_id.client_id) {
|
||||
Ok(id) => sign_up_combined(
|
||||
SignUpData {
|
||||
username: data.username,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::app_state::Schedule;
|
||||
use crate::parser::parse_xls;
|
||||
use crate::routes::schedule::schema::CacheStatus;
|
||||
use crate::routes::schema::{IntoResponseAsError, ResponseError};
|
||||
use crate::xls_downloader::interface::XLSDownloader;
|
||||
use crate::xls_downloader::interface::{FetchError, XLSDownloader};
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{patch, web};
|
||||
use chrono::Utc;
|
||||
@@ -41,7 +41,7 @@ pub async fn update_download_url(
|
||||
}
|
||||
|
||||
match downloader.fetch(false).await {
|
||||
Ok(download_result) => match parse_xls(download_result.data.as_ref().unwrap()) {
|
||||
Ok(download_result) => match parse_xls(&download_result.data.unwrap()) {
|
||||
Ok(data) => {
|
||||
*schedule = Some(Schedule {
|
||||
etag: download_result.etag,
|
||||
@@ -53,21 +53,27 @@ pub async fn update_download_url(
|
||||
|
||||
Ok(CacheStatus::from(schedule.as_ref().unwrap())).into()
|
||||
}
|
||||
Err(error) => ErrorCode::InvalidSchedule(error).into_response(),
|
||||
Err(error) => {
|
||||
sentry::capture_error(&error);
|
||||
|
||||
ErrorCode::InvalidSchedule(error).into_response()
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
eprintln!("Unknown url provided {}", data.url);
|
||||
eprintln!("{:?}", error);
|
||||
if let FetchError::Unknown(error) = &error {
|
||||
sentry::capture_error(&error);
|
||||
}
|
||||
|
||||
ErrorCode::DownloadFailed.into_response()
|
||||
ErrorCode::DownloadFailed(error).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("Unknown url provided {}", data.url);
|
||||
eprintln!("{:?}", error);
|
||||
if let FetchError::Unknown(error) = &error {
|
||||
sentry::capture_error(&error);
|
||||
}
|
||||
|
||||
ErrorCode::FetchFailed.into_response()
|
||||
ErrorCode::FetchFailed(error).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +85,7 @@ mod schema {
|
||||
use derive_more::Display;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use utoipa::ToSchema;
|
||||
use crate::xls_downloader::interface::FetchError;
|
||||
|
||||
pub type ServiceResponse = crate::routes::schema::Response<CacheStatus, ErrorCode>;
|
||||
|
||||
@@ -93,16 +100,16 @@ mod schema {
|
||||
#[schema(as = SetDownloadUrl::ErrorCode)]
|
||||
pub enum ErrorCode {
|
||||
/// Transferred link with host different from politehnikum-eng.ru.
|
||||
#[display("URL with unknown host provided. Provide url with politehnikum-eng.ru host.")]
|
||||
#[display("URL with unknown host provided. Provide url with 'politehnikum-eng.ru' host.")]
|
||||
NonWhitelistedHost,
|
||||
|
||||
/// Failed to retrieve file metadata.
|
||||
#[display("Unable to retrieve metadata from the specified URL.")]
|
||||
FetchFailed,
|
||||
#[display("Unable to retrieve metadata from the specified URL: {_0}")]
|
||||
FetchFailed(FetchError),
|
||||
|
||||
/// Failed to download the file.
|
||||
#[display("Unable to retrieve data from the specified URL.")]
|
||||
DownloadFailed,
|
||||
#[display("Unable to retrieve data from the specified URL: {_0}")]
|
||||
DownloadFailed(FetchError),
|
||||
|
||||
/// The link leads to an outdated schedule.
|
||||
///
|
||||
@@ -112,7 +119,7 @@ mod schema {
|
||||
OutdatedSchedule,
|
||||
|
||||
/// Failed to parse the schedule.
|
||||
#[display("{}", "_0.display()")]
|
||||
#[display("{_0}")]
|
||||
InvalidSchedule(ParseError),
|
||||
}
|
||||
|
||||
@@ -123,8 +130,8 @@ mod schema {
|
||||
{
|
||||
match self {
|
||||
ErrorCode::NonWhitelistedHost => serializer.serialize_str("NON_WHITELISTED_HOST"),
|
||||
ErrorCode::FetchFailed => serializer.serialize_str("FETCH_FAILED"),
|
||||
ErrorCode::DownloadFailed => serializer.serialize_str("DOWNLOAD_FAILED"),
|
||||
ErrorCode::FetchFailed(_) => serializer.serialize_str("FETCH_FAILED"),
|
||||
ErrorCode::DownloadFailed(_) => serializer.serialize_str("DOWNLOAD_FAILED"),
|
||||
ErrorCode::OutdatedSchedule => serializer.serialize_str("OUTDATED_SCHEDULE"),
|
||||
ErrorCode::InvalidSchedule(_) => serializer.serialize_str("INVALID_SCHEDULE"),
|
||||
}
|
||||
|
||||
@@ -59,13 +59,16 @@ async fn oauth(data: web::Json<Request>, app_state: web::Data<AppState>) -> Serv
|
||||
return ErrorCode::VkIdError.into_response();
|
||||
}
|
||||
|
||||
if let Ok(auth_data) = res.json::<VkIdAuthResponse>().await {
|
||||
Ok(Response {
|
||||
access_token: auth_data.id_token,
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
ErrorCode::VkIdError.into_response()
|
||||
match res.json::<VkIdAuthResponse>().await {
|
||||
Ok(auth_data) =>
|
||||
Ok(Response {
|
||||
access_token: auth_data.id_token,
|
||||
}).into(),
|
||||
Err(error) => {
|
||||
sentry::capture_error(&error);
|
||||
|
||||
ErrorCode::VkIdError.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => ErrorCode::VkIdError.into_response(),
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use crate::xls_downloader::interface::{FetchError, FetchOk, FetchResult, XLSDownloader};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct BasicXlsDownloader {
|
||||
pub url: Option<String>,
|
||||
user_agent: String,
|
||||
}
|
||||
|
||||
async fn fetch_specified(url: &String, user_agent: String, head: bool) -> FetchResult {
|
||||
async fn fetch_specified(url: &String, user_agent: &String, head: bool) -> FetchResult {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response = if head {
|
||||
@@ -13,14 +16,14 @@ async fn fetch_specified(url: &String, user_agent: String, head: bool) -> FetchR
|
||||
} else {
|
||||
client.get(url)
|
||||
}
|
||||
.header("User-Agent", user_agent)
|
||||
.header("User-Agent", user_agent.clone())
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(r) => {
|
||||
if r.status().as_u16() != 200 {
|
||||
return Err(FetchError::BadStatusCode);
|
||||
return Err(FetchError::BadStatusCode(r.status().as_u16()));
|
||||
}
|
||||
|
||||
let headers = r.headers();
|
||||
@@ -30,11 +33,18 @@ async fn fetch_specified(url: &String, user_agent: String, head: bool) -> FetchR
|
||||
let last_modified = headers.get("last-modified");
|
||||
let date = headers.get("date");
|
||||
|
||||
if content_type.is_none() || etag.is_none() || last_modified.is_none() || date.is_none()
|
||||
{
|
||||
Err(FetchError::BadHeaders)
|
||||
if content_type.is_none() {
|
||||
Err(FetchError::BadHeaders("Content-Type".to_string()))
|
||||
} else if etag.is_none() {
|
||||
Err(FetchError::BadHeaders("ETag".to_string()))
|
||||
} else if last_modified.is_none() {
|
||||
Err(FetchError::BadHeaders("Last-Modified".to_string()))
|
||||
} else if date.is_none() {
|
||||
Err(FetchError::BadHeaders("Date".to_string()))
|
||||
} else if content_type.unwrap() != "application/vnd.ms-excel" {
|
||||
Err(FetchError::BadContentType)
|
||||
Err(FetchError::BadContentType(
|
||||
content_type.unwrap().to_str().unwrap().to_string(),
|
||||
))
|
||||
} else {
|
||||
let etag = etag.unwrap().to_str().unwrap().to_string();
|
||||
let last_modified =
|
||||
@@ -49,13 +59,16 @@ async fn fetch_specified(url: &String, user_agent: String, head: bool) -> FetchR
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(_) => Err(FetchError::Unknown),
|
||||
Err(error) => Err(FetchError::Unknown(Arc::new(error))),
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicXlsDownloader {
|
||||
pub fn new() -> Self {
|
||||
BasicXlsDownloader { url: None }
|
||||
BasicXlsDownloader {
|
||||
url: None,
|
||||
user_agent: env::var("REQWEST_USER_AGENT").expect("USER_AGENT must be set"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,17 +77,12 @@ impl XLSDownloader for BasicXlsDownloader {
|
||||
if self.url.is_none() {
|
||||
Err(FetchError::NoUrlProvided)
|
||||
} else {
|
||||
fetch_specified(
|
||||
self.url.as_ref().unwrap(),
|
||||
"t.me/polytechnic_next".to_string(),
|
||||
head,
|
||||
)
|
||||
.await
|
||||
fetch_specified(self.url.as_ref().unwrap(), &self.user_agent, head).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_url(&mut self, url: String) -> FetchResult {
|
||||
let result = fetch_specified(&url, "t.me/polytechnic_next".to_string(), true).await;
|
||||
let result = fetch_specified(&url, &self.user_agent, true).await;
|
||||
|
||||
if let Ok(_) = result {
|
||||
self.url = Some(url);
|
||||
@@ -86,7 +94,7 @@ impl XLSDownloader for BasicXlsDownloader {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::xls_downloader::basic_impl::{BasicXlsDownloader, fetch_specified};
|
||||
use crate::xls_downloader::basic_impl::{fetch_specified, BasicXlsDownloader};
|
||||
use crate::xls_downloader::interface::{FetchError, XLSDownloader};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -95,8 +103,8 @@ mod tests {
|
||||
let user_agent = String::new();
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, user_agent.clone(), true).await,
|
||||
fetch_specified(&url, user_agent.clone(), false).await,
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
@@ -109,21 +117,17 @@ mod tests {
|
||||
let user_agent = String::new();
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, user_agent.clone(), true).await,
|
||||
fetch_specified(&url, user_agent.clone(), false).await,
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
assert!(results[1].is_err());
|
||||
|
||||
assert_eq!(
|
||||
*results[0].as_ref().err().unwrap(),
|
||||
FetchError::BadStatusCode
|
||||
);
|
||||
assert_eq!(
|
||||
*results[1].as_ref().err().unwrap(),
|
||||
FetchError::BadStatusCode
|
||||
);
|
||||
let expected_error = FetchError::BadStatusCode(404);
|
||||
|
||||
assert_eq!(*results[0].as_ref().err().unwrap(), expected_error);
|
||||
assert_eq!(*results[1].as_ref().err().unwrap(), expected_error);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -132,15 +136,17 @@ mod tests {
|
||||
let user_agent = String::new();
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, user_agent.clone(), true).await,
|
||||
fetch_specified(&url, user_agent.clone(), false).await,
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
assert!(results[1].is_err());
|
||||
|
||||
assert_eq!(*results[0].as_ref().err().unwrap(), FetchError::BadHeaders);
|
||||
assert_eq!(*results[1].as_ref().err().unwrap(), FetchError::BadHeaders);
|
||||
let expected_error = FetchError::BadHeaders("ETag".to_string());
|
||||
|
||||
assert_eq!(*results[0].as_ref().err().unwrap(), expected_error);
|
||||
assert_eq!(*results[1].as_ref().err().unwrap(), expected_error);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -149,21 +155,12 @@ mod tests {
|
||||
let user_agent = String::new();
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, user_agent.clone(), true).await,
|
||||
fetch_specified(&url, user_agent.clone(), false).await,
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_err());
|
||||
assert!(results[1].is_err());
|
||||
|
||||
assert_eq!(
|
||||
*results[0].as_ref().err().unwrap(),
|
||||
FetchError::BadContentType
|
||||
);
|
||||
assert_eq!(
|
||||
*results[1].as_ref().err().unwrap(),
|
||||
FetchError::BadContentType
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -172,8 +169,8 @@ mod tests {
|
||||
let user_agent = String::new();
|
||||
|
||||
let results = [
|
||||
fetch_specified(&url, user_agent.clone(), true).await,
|
||||
fetch_specified(&url, user_agent.clone(), false).await,
|
||||
fetch_specified(&url, &user_agent, true).await,
|
||||
fetch_specified(&url, &user_agent, false).await,
|
||||
];
|
||||
|
||||
assert!(results[0].is_ok());
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::Display;
|
||||
use std::mem::discriminant;
|
||||
use std::sync::Arc;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// XLS data retrieval errors.
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(Clone, Debug, ToSchema, Display)]
|
||||
pub enum FetchError {
|
||||
/// File url is not set.
|
||||
#[display("The link to the timetable was not provided earlier.")]
|
||||
NoUrlProvided,
|
||||
|
||||
/// Unknown error.
|
||||
Unknown,
|
||||
#[display("An unknown error occurred while downloading the file.")]
|
||||
#[schema(value_type = String)]
|
||||
Unknown(Arc<reqwest::Error>),
|
||||
|
||||
/// Server returned a status code different from 200.
|
||||
BadStatusCode,
|
||||
#[display("Server returned a status code {_0}.")]
|
||||
BadStatusCode(u16),
|
||||
|
||||
/// The url leads to a file of a different type.
|
||||
BadContentType,
|
||||
#[display("The link leads to a file of type '{_0}'.")]
|
||||
BadContentType(String),
|
||||
|
||||
/// Server doesn't return expected headers.
|
||||
BadHeaders,
|
||||
#[display("Server doesn't return expected header(s) '{_0}'.")]
|
||||
BadHeaders(String),
|
||||
}
|
||||
|
||||
impl PartialEq for FetchError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
discriminant(self) == discriminant(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of XLS data retrieval.
|
||||
|
||||
Reference in New Issue
Block a user