mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-07 02:07:48 +03:00
Compare commits
1 Commits
release/v1
...
2ff6afec32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ff6afec32 |
169
.github/workflows/release.yml
vendored
169
.github/workflows/release.yml
vendored
@@ -1,169 +0,0 @@
|
|||||||
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
|
|
||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -1,9 +1,10 @@
|
|||||||
name: cargo test
|
name: Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
tags-ignore: [ "release/v*" ]
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -29,4 +30,3 @@ jobs:
|
|||||||
JWT_SECRET: "test-secret-at-least-256-bits-used"
|
JWT_SECRET: "test-secret-at-least-256-bits-used"
|
||||||
VKID_CLIENT_ID: 0
|
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
Normal file
9
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?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,16 +872,6 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"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]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -926,9 +916,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diesel"
|
name = "diesel"
|
||||||
version = "2.2.8"
|
version = "2.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "470eb10efc8646313634c99bb1593f402a6434cbd86e266770c6e39219adb86a"
|
checksum = "34d3950690ba3a6910126162b47e775e203006d4242a15de912bec6c0a695153"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder 1.5.0",
|
"byteorder 1.5.0",
|
||||||
@@ -1082,22 +1072,10 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
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]]
|
[[package]]
|
||||||
name = "firebase-messaging-rs"
|
name = "firebase-messaging-rs"
|
||||||
version = "0.8.10"
|
version = "0.8.10"
|
||||||
source = "git+https://github.com/i10416/firebase-messaging-rs.git#f2cb78b0bda33a41962a1a2ed178fb9c5be59e6a"
|
source = "git+ssh://git@github.com/i10416/firebase-messaging-rs.git#f2cb78b0bda33a41962a1a2ed178fb9c5be59e6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -1426,17 +1404,6 @@ dependencies = [
|
|||||||
"winutil",
|
"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]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@@ -1950,12 +1917,6 @@ version = "0.2.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
|
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.171"
|
version = "0.2.171"
|
||||||
@@ -2129,8 +2090,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "22a605e0778d73324c897e7e4bd5903f64639884a99d1bad55bccfd986260063"
|
checksum = "22a605e0778d73324c897e7e4bd5903f64639884a99d1bad55bccfd986260063"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 0.3.13",
|
"byteorder 0.3.13",
|
||||||
"hostname 0.1.5",
|
"hostname",
|
||||||
"lazy_static 0.2.11",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"rand 0.3.23",
|
"rand 0.3.23",
|
||||||
@@ -2194,17 +2155,6 @@ dependencies = [
|
|||||||
"vcpkg",
|
"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]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -2441,7 +2391,7 @@ dependencies = [
|
|||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -2461,7 +2411,7 @@ dependencies = [
|
|||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
@@ -2677,7 +2627,6 @@ dependencies = [
|
|||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2 0.4.8",
|
"h2 0.4.8",
|
||||||
@@ -2766,15 +2715,6 @@ version = "0.3.25"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401"
|
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]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -2876,7 +2816,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schedule-parser-rusted"
|
name = "schedule-parser-rusted"
|
||||||
version = "1.0.2"
|
version = "0.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-macros 0.1.0",
|
"actix-macros 0.1.0",
|
||||||
"actix-test",
|
"actix-test",
|
||||||
@@ -2900,8 +2840,6 @@ dependencies = [
|
|||||||
"rand 0.9.0",
|
"rand 0.9.0",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"sentry",
|
|
||||||
"sentry-actix",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@@ -2969,133 +2907,6 @@ dependencies = [
|
|||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
@@ -3232,7 +3043,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
"time 0.3.40",
|
"time 0.3.40",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3355,33 +3166,13 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.12",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.100",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3677,16 +3468,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
@@ -3701,15 +3482,6 @@ version = "1.18.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uname"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.8.1"
|
version = "2.8.1"
|
||||||
@@ -3734,19 +3506,6 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
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]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
@@ -3756,7 +3515,6 @@ dependencies = [
|
|||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3831,15 +3589,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.2",
|
"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]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -4367,7 +4118,7 @@ dependencies = [
|
|||||||
"flate2",
|
"flate2",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.8.0",
|
||||||
"memchr",
|
"memchr",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
"zopfli",
|
"zopfli",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
11
Cargo.toml
11
Cargo.toml
@@ -3,13 +3,10 @@ members = ["actix-macros", "actix-test"]
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "schedule-parser-rusted"
|
name = "schedule-parser-rusted"
|
||||||
version = "1.0.2"
|
version = "0.8.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
debug = true
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.10.2"
|
actix-web = "4.10.2"
|
||||||
actix-macros = { path = "actix-macros" }
|
actix-macros = { path = "actix-macros" }
|
||||||
@@ -17,11 +14,11 @@ bcrypt = "0.17.0"
|
|||||||
calamine = "0.26.1"
|
calamine = "0.26.1"
|
||||||
chrono = { version = "0.4.40", features = ["serde"] }
|
chrono = { version = "0.4.40", features = ["serde"] }
|
||||||
derive_more = "2.0.1"
|
derive_more = "2.0.1"
|
||||||
diesel = { version = "2.2.8", features = ["postgres"] }
|
diesel = { version = "2.2.9", features = ["postgres"] }
|
||||||
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
env_logger = "0.11.7"
|
env_logger = "0.11.7"
|
||||||
firebase-messaging-rs = { git = "https://github.com/i10416/firebase-messaging-rs.git" }
|
firebase-messaging-rs = { git = "ssh://git@github.com/i10416/firebase-messaging-rs.git" }
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
fuzzy-matcher = "0.3.7"
|
fuzzy-matcher = "0.3.7"
|
||||||
jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
|
jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
|
||||||
@@ -30,8 +27,6 @@ mime = "0.3.17"
|
|||||||
objectid = "0.2.0"
|
objectid = "0.2.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
reqwest = { version = "0.12.15", features = ["json"] }
|
reqwest = { version = "0.12.15", features = ["json"] }
|
||||||
sentry = "0.37.0"
|
|
||||||
sentry-actix = "0.37.0"
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
serde_with = "3.12.0"
|
serde_with = "3.12.0"
|
||||||
|
|||||||
14
Dockerfile
14
Dockerfile
@@ -1,14 +0,0 @@
|
|||||||
FROM debian:stable-slim
|
|
||||||
LABEL authors="n08i40k"
|
|
||||||
|
|
||||||
ARG BINARY_NAME
|
|
||||||
|
|
||||||
WORKDIR /app/
|
|
||||||
|
|
||||||
RUN apt update && \
|
|
||||||
apt install -y libpq5
|
|
||||||
|
|
||||||
COPY ./${BINARY_NAME} /bin/main
|
|
||||||
RUN chmod +x /bin/main
|
|
||||||
|
|
||||||
ENTRYPOINT ["main"]
|
|
||||||
40
src/main.rs
40
src/main.rs
@@ -4,7 +4,6 @@ use crate::middlewares::content_type::ContentTypeBootstrap;
|
|||||||
use actix_web::dev::{ServiceFactory, ServiceRequest};
|
use actix_web::dev::{ServiceFactory, ServiceRequest};
|
||||||
use actix_web::{App, Error, HttpServer};
|
use actix_web::{App, Error, HttpServer};
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use std::io;
|
|
||||||
use utoipa_actix_web::AppExt;
|
use utoipa_actix_web::AppExt;
|
||||||
use utoipa_actix_web::scope::Scope;
|
use utoipa_actix_web::scope::Scope;
|
||||||
use utoipa_rapidoc::RapiDoc;
|
use utoipa_rapidoc::RapiDoc;
|
||||||
@@ -70,8 +69,12 @@ pub fn get_api_scope<
|
|||||||
.service(vk_id_scope)
|
.service(vk_id_scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn async_main() -> io::Result<()> {
|
#[actix_web::main]
|
||||||
println!("Starting server...");
|
async fn main() {
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
unsafe { std::env::set_var("RUST_LOG", "debug") };
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let app_state = app_state().await;
|
let app_state = app_state().await;
|
||||||
|
|
||||||
@@ -79,11 +82,7 @@ async fn async_main() -> io::Result<()> {
|
|||||||
let (app, api) = App::new()
|
let (app, api) = App::new()
|
||||||
.into_utoipa_app()
|
.into_utoipa_app()
|
||||||
.app_data(app_state.clone())
|
.app_data(app_state.clone())
|
||||||
.service(
|
.service(get_api_scope("/api/v1").wrap(ContentTypeBootstrap))
|
||||||
get_api_scope("/api/v1")
|
|
||||||
.wrap(sentry_actix::Sentry::new())
|
|
||||||
.wrap(ContentTypeBootstrap),
|
|
||||||
)
|
|
||||||
.split_for_parts();
|
.split_for_parts();
|
||||||
|
|
||||||
let rapidoc_service = RapiDoc::with_openapi("/api-docs-json", api).path("/api-docs");
|
let rapidoc_service = RapiDoc::with_openapi("/api-docs-json", api).path("/api-docs");
|
||||||
@@ -97,28 +96,9 @@ async fn async_main() -> io::Result<()> {
|
|||||||
app.service(rapidoc_service.custom_html(patched_rapidoc_html))
|
app.service(rapidoc_service.custom_html(patched_rapidoc_html))
|
||||||
})
|
})
|
||||||
.workers(4)
|
.workers(4)
|
||||||
.bind(("0.0.0.0", 5050))?
|
.bind(("0.0.0.0", 5050))
|
||||||
|
.unwrap()
|
||||||
.run()
|
.run()
|
||||||
.await
|
.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,13 +1,12 @@
|
|||||||
use crate::parser::LessonParseResult::{Lessons, Street};
|
|
||||||
use crate::parser::schema::LessonType::Break;
|
use crate::parser::schema::LessonType::Break;
|
||||||
use crate::parser::schema::{
|
use crate::parser::schema::{
|
||||||
Day, ErrorCell, ErrorCellPos, Lesson, LessonSubGroup, LessonTime, LessonType, ParseError,
|
Day, Lesson, LessonSubGroup, LessonTime, LessonType, ParseError, ParseResult, ScheduleEntry,
|
||||||
ParseResult, ScheduleEntry,
|
|
||||||
};
|
};
|
||||||
use calamine::{Reader, Xls, open_workbook_from_rs};
|
use crate::parser::LessonParseResult::{Lessons, Street};
|
||||||
|
use calamine::{open_workbook_from_rs, Reader, Xls};
|
||||||
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
|
||||||
use fuzzy_matcher::FuzzyMatcher;
|
|
||||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||||
|
use fuzzy_matcher::FuzzyMatcher;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
@@ -57,8 +56,9 @@ fn get_string_from_cell(worksheet: &WorkSheet, row: u32, col: u32) -> Option<Str
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NL_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[\n\r]+").unwrap());
|
static NL_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
static SP_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\s+").unwrap());
|
LazyLock::new(|| Regex::new(r"[\n\r]+").unwrap());
|
||||||
|
static SP_RE: LazyLock<Regex, fn() -> Regex> = LazyLock::new(|| Regex::new(r"\s+").unwrap());
|
||||||
|
|
||||||
let trimmed_data = SP_RE
|
let trimmed_data = SP_RE
|
||||||
.replace_all(&NL_RE.replace_all(&cell_data, " "), " ")
|
.replace_all(&NL_RE.replace_all(&cell_data, " "), " ")
|
||||||
@@ -252,7 +252,7 @@ fn parse_lesson(
|
|||||||
|
|
||||||
let raw_name = raw_name_opt.unwrap();
|
let raw_name = raw_name_opt.unwrap();
|
||||||
|
|
||||||
static OTHER_STREET_RE: LazyLock<Regex> =
|
static OTHER_STREET_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
LazyLock::new(|| Regex::new(r"^[А-Я][а-я]+,?\s?[0-9]+$").unwrap());
|
LazyLock::new(|| Regex::new(r"^[А-Я][а-я]+,?\s?[0-9]+$").unwrap());
|
||||||
|
|
||||||
if OTHER_STREET_RE.is_match(&raw_name) {
|
if OTHER_STREET_RE.is_match(&raw_name) {
|
||||||
@@ -275,9 +275,7 @@ fn parse_lesson(
|
|||||||
.filter(|time| time.xls_range.1.0 == cell_range.1.0)
|
.filter(|time| time.xls_range.1.0 == cell_range.1.0)
|
||||||
.collect::<Vec<&InternalTime>>();
|
.collect::<Vec<&InternalTime>>();
|
||||||
|
|
||||||
let end_time = end_time_arr
|
let end_time = end_time_arr.first().ok_or(ParseError::LessonTimeNotFound)?;
|
||||||
.first()
|
|
||||||
.ok_or(ParseError::LessonTimeNotFound(ErrorCellPos { row, column }))?;
|
|
||||||
|
|
||||||
let range: Option<[u8; 2]> = if time.default_index != None {
|
let range: Option<[u8; 2]> = if time.default_index != None {
|
||||||
let default = time.default_index.unwrap() as u8;
|
let default = time.default_index.unwrap() as u8;
|
||||||
@@ -391,12 +389,14 @@ 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.
|
/// 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> {
|
fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup>), ParseError> {
|
||||||
static LESSON_RE: LazyLock<Regex> =
|
static LESSON_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
LazyLock::new(|| Regex::new(r"(?:[А-Я][а-я]+[А-Я]{2}(?:\([0-9][а-я]+\))?)+$").unwrap());
|
LazyLock::new(|| Regex::new(r"(?:[А-Я][а-я]+[А-Я]{2}(?:\([0-9][а-я]+\))?)+$").unwrap());
|
||||||
static TEACHER_RE: LazyLock<Regex> =
|
static TEACHER_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
LazyLock::new(|| Regex::new(r"([А-Я][а-я]+)([А-Я])([А-Я])(?:\(([0-9])[а-я]+\))?").unwrap());
|
LazyLock::new(|| Regex::new(r"([А-Я][а-я]+)([А-Я])([А-Я])(?:\(([0-9])[а-я]+\))?").unwrap());
|
||||||
static CLEAN_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[\s.,]+").unwrap());
|
static CLEAN_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
static END_CLEAN_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[.\s]+$").unwrap());
|
LazyLock::new(|| Regex::new(r"[\s.,]+").unwrap());
|
||||||
|
static END_CLEAN_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r"[.\s]+$").unwrap());
|
||||||
|
|
||||||
let (teachers, lesson_name) = {
|
let (teachers, lesson_name) = {
|
||||||
let clean_name = CLEAN_RE.replace_all(&name, "").to_string();
|
let clean_name = CLEAN_RE.replace_all(&name, "").to_string();
|
||||||
@@ -423,9 +423,14 @@ fn parse_name_and_subgroups(name: &String) -> Result<(String, Vec<LessonSubGroup
|
|||||||
|
|
||||||
for captures in teacher_it {
|
for captures in teacher_it {
|
||||||
subgroups.push(LessonSubGroup {
|
subgroups.push(LessonSubGroup {
|
||||||
number: match captures.get(4) {
|
number: if let Some(capture) = captures.get(4) {
|
||||||
Some(capture) => capture.as_str().to_string().parse::<u8>().unwrap(),
|
capture
|
||||||
None => 0,
|
.as_str()
|
||||||
|
.to_string()
|
||||||
|
.parse::<u8>()
|
||||||
|
.map_err(|_| ParseError::SubgroupIndexParsingFailed)?
|
||||||
|
} else {
|
||||||
|
0
|
||||||
},
|
},
|
||||||
cabinet: None,
|
cabinet: None,
|
||||||
teacher: format!(
|
teacher: format!(
|
||||||
@@ -660,12 +665,10 @@ pub fn parse_xls(buffer: &Vec<u8>) -> Result<ParseResult, ParseError> {
|
|||||||
|
|
||||||
// time
|
// time
|
||||||
let time_range = {
|
let time_range = {
|
||||||
static TIME_RE: LazyLock<Regex> =
|
static TIME_RE: LazyLock<Regex, fn() -> Regex> =
|
||||||
LazyLock::new(|| Regex::new(r"(\d+\.\d+)-(\d+\.\d+)").unwrap());
|
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_match = parse_res.get(1).unwrap().as_str();
|
||||||
let start_parts: Vec<&str> = start_match.split(".").collect();
|
let start_parts: Vec<&str> = start_match.split(".").collect();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use derive_more::{Display, Error};
|
use derive_more::Display;
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -115,33 +115,10 @@ pub struct ParseResult {
|
|||||||
pub teachers: HashMap<String, ScheduleEntry>,
|
pub teachers: HashMap<String, ScheduleEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Display, Error, ToSchema)]
|
#[derive(Debug, Display, Clone, 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 {
|
pub enum ParseError {
|
||||||
/// Errors related to reading XLS file.
|
/// Errors related to reading XLS file.
|
||||||
#[display("{_0:?}: Failed to read XLS file.")]
|
#[display("{}: Failed to read XLS file.", "_0")]
|
||||||
#[schema(value_type = String)]
|
#[schema(value_type = String)]
|
||||||
BadXLS(Arc<calamine::XlsError>),
|
BadXLS(Arc<calamine::XlsError>),
|
||||||
|
|
||||||
@@ -154,12 +131,16 @@ pub enum ParseError {
|
|||||||
UnknownWorkSheetRange,
|
UnknownWorkSheetRange,
|
||||||
|
|
||||||
/// Failed to read the beginning and end of the lesson from the line
|
/// Failed to read the beginning and end of the lesson from the line
|
||||||
#[display("Failed to read lesson start and end times from {_0}.")]
|
#[display("Failed to read lesson start and end times from string.")]
|
||||||
GlobalTime(ErrorCell),
|
GlobalTime,
|
||||||
|
|
||||||
/// Not found the beginning and the end corresponding to the lesson.
|
/// Not found the beginning and the end corresponding to the lesson.
|
||||||
#[display("No start and end times matching the lesson (at {_0}) was found.")]
|
#[display("No start and end times matching the lesson was found.")]
|
||||||
LessonTimeNotFound(ErrorCellPos),
|
LessonTimeNotFound,
|
||||||
|
|
||||||
|
/// Failed to read the subgroup index.
|
||||||
|
#[display("Failed to read subgroup index.")]
|
||||||
|
SubgroupIndexParsingFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for ParseError {
|
impl Serialize for ParseError {
|
||||||
@@ -173,8 +154,11 @@ impl Serialize for ParseError {
|
|||||||
ParseError::UnknownWorkSheetRange => {
|
ParseError::UnknownWorkSheetRange => {
|
||||||
serializer.serialize_str("UNKNOWN_WORK_SHEET_RANGE")
|
serializer.serialize_str("UNKNOWN_WORK_SHEET_RANGE")
|
||||||
}
|
}
|
||||||
ParseError::GlobalTime(_) => serializer.serialize_str("GLOBAL_TIME"),
|
ParseError::GlobalTime => serializer.serialize_str("GLOBAL_TIME"),
|
||||||
ParseError::LessonTimeNotFound(_) => serializer.serialize_str("LESSON_TIME_NOT_FOUND"),
|
ParseError::LessonTimeNotFound => serializer.serialize_str("LESSON_TIME_NOT_FOUND"),
|
||||||
|
ParseError::SubgroupIndexParsingFailed => {
|
||||||
|
serializer.serialize_str("SUBGROUP_INDEX_PARSING_FAILED")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
use crate::utility::jwt::DEFAULT_ALGORITHM;
|
||||||
use jsonwebtoken::errors::ErrorKind;
|
use jsonwebtoken::errors::ErrorKind;
|
||||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
struct TokenData {
|
struct TokenData {
|
||||||
@@ -14,7 +17,7 @@ struct TokenData {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Claims {
|
struct Claims {
|
||||||
sub: i32,
|
sub: String,
|
||||||
iis: String,
|
iis: String,
|
||||||
jti: i32,
|
jti: i32,
|
||||||
app: i32,
|
app: i32,
|
||||||
@@ -49,10 +52,17 @@ const VK_PUBLIC_KEY: &str = concat!(
|
|||||||
"-----END PUBLIC KEY-----"
|
"-----END PUBLIC KEY-----"
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn parse_vk_id(token_str: &String, client_id: i32) -> Result<i32, Error> {
|
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> {
|
||||||
let dkey = DecodingKey::from_rsa_pem(VK_PUBLIC_KEY.as_bytes()).unwrap();
|
let dkey = DecodingKey::from_rsa_pem(VK_PUBLIC_KEY.as_bytes()).unwrap();
|
||||||
|
|
||||||
match decode::<Claims>(&token_str, &dkey, &Validation::new(Algorithm::RS256)) {
|
match decode::<Claims>(&token_str, &dkey, &Validation::new(DEFAULT_ALGORITHM)) {
|
||||||
Ok(token_data) => {
|
Ok(token_data) => {
|
||||||
let claims = token_data.claims;
|
let claims = token_data.claims;
|
||||||
|
|
||||||
@@ -60,10 +70,13 @@ pub fn parse_vk_id(token_str: &String, client_id: i32) -> Result<i32, Error> {
|
|||||||
Err(Error::UnknownIssuer(claims.iis))
|
Err(Error::UnknownIssuer(claims.iis))
|
||||||
} else if claims.jti != 21 {
|
} else if claims.jti != 21 {
|
||||||
Err(Error::UnknownType(claims.jti))
|
Err(Error::UnknownType(claims.jti))
|
||||||
} else if claims.app != client_id {
|
} else if claims.app != *VK_ID_CLIENT_ID {
|
||||||
Err(Error::UnknownClientId(claims.app))
|
Err(Error::UnknownClientId(claims.app))
|
||||||
} else {
|
} else {
|
||||||
Ok(claims.sub)
|
match claims.sub.parse::<i32>() {
|
||||||
|
Ok(sub) => Ok(sub),
|
||||||
|
Err(_) => Err(Error::InvalidToken),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => Err(match err.into_kind() {
|
Err(err) => Err(match err.into_kind() {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ pub async fn sign_in_vk(
|
|||||||
) -> ServiceResponse {
|
) -> ServiceResponse {
|
||||||
let data = data_json.into_inner();
|
let data = data_json.into_inner();
|
||||||
|
|
||||||
match parse_vk_id(&data.access_token, app_state.vk_id.client_id) {
|
match parse_vk_id(&data.access_token) {
|
||||||
Ok(id) => sign_in_combined(Vk(id), &app_state).await.into(),
|
Ok(id) => sign_in_combined(Vk(id), &app_state).await.into(),
|
||||||
Err(_) => ErrorCode::InvalidVkAccessToken.into_response(),
|
Err(_) => ErrorCode::InvalidVkAccessToken.into_response(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ pub async fn sign_up_vk(
|
|||||||
) -> ServiceResponse {
|
) -> ServiceResponse {
|
||||||
let data = data_json.into_inner();
|
let data = data_json.into_inner();
|
||||||
|
|
||||||
match parse_vk_id(&data.access_token, app_state.vk_id.client_id) {
|
match parse_vk_id(&data.access_token) {
|
||||||
Ok(id) => sign_up_combined(
|
Ok(id) => sign_up_combined(
|
||||||
SignUpData {
|
SignUpData {
|
||||||
username: data.username,
|
username: data.username,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::app_state::Schedule;
|
|||||||
use crate::parser::parse_xls;
|
use crate::parser::parse_xls;
|
||||||
use crate::routes::schedule::schema::CacheStatus;
|
use crate::routes::schedule::schema::CacheStatus;
|
||||||
use crate::routes::schema::{IntoResponseAsError, ResponseError};
|
use crate::routes::schema::{IntoResponseAsError, ResponseError};
|
||||||
use crate::xls_downloader::interface::{FetchError, XLSDownloader};
|
use crate::xls_downloader::interface::XLSDownloader;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use actix_web::{patch, web};
|
use actix_web::{patch, web};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -41,7 +41,7 @@ pub async fn update_download_url(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match downloader.fetch(false).await {
|
match downloader.fetch(false).await {
|
||||||
Ok(download_result) => match parse_xls(&download_result.data.unwrap()) {
|
Ok(download_result) => match parse_xls(download_result.data.as_ref().unwrap()) {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
*schedule = Some(Schedule {
|
*schedule = Some(Schedule {
|
||||||
etag: download_result.etag,
|
etag: download_result.etag,
|
||||||
@@ -53,27 +53,21 @@ pub async fn update_download_url(
|
|||||||
|
|
||||||
Ok(CacheStatus::from(schedule.as_ref().unwrap())).into()
|
Ok(CacheStatus::from(schedule.as_ref().unwrap())).into()
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => ErrorCode::InvalidSchedule(error).into_response(),
|
||||||
sentry::capture_error(&error);
|
|
||||||
|
|
||||||
ErrorCode::InvalidSchedule(error).into_response()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if let FetchError::Unknown(error) = &error {
|
eprintln!("Unknown url provided {}", data.url);
|
||||||
sentry::capture_error(&error);
|
eprintln!("{:?}", error);
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode::DownloadFailed(error).into_response()
|
ErrorCode::DownloadFailed.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if let FetchError::Unknown(error) = &error {
|
eprintln!("Unknown url provided {}", data.url);
|
||||||
sentry::capture_error(&error);
|
eprintln!("{:?}", error);
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode::FetchFailed(error).into_response()
|
ErrorCode::FetchFailed.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +79,6 @@ mod schema {
|
|||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
use crate::xls_downloader::interface::FetchError;
|
|
||||||
|
|
||||||
pub type ServiceResponse = crate::routes::schema::Response<CacheStatus, ErrorCode>;
|
pub type ServiceResponse = crate::routes::schema::Response<CacheStatus, ErrorCode>;
|
||||||
|
|
||||||
@@ -100,16 +93,16 @@ mod schema {
|
|||||||
#[schema(as = SetDownloadUrl::ErrorCode)]
|
#[schema(as = SetDownloadUrl::ErrorCode)]
|
||||||
pub enum ErrorCode {
|
pub enum ErrorCode {
|
||||||
/// Transferred link with host different from politehnikum-eng.ru.
|
/// 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,
|
NonWhitelistedHost,
|
||||||
|
|
||||||
/// Failed to retrieve file metadata.
|
/// Failed to retrieve file metadata.
|
||||||
#[display("Unable to retrieve metadata from the specified URL: {_0}")]
|
#[display("Unable to retrieve metadata from the specified URL.")]
|
||||||
FetchFailed(FetchError),
|
FetchFailed,
|
||||||
|
|
||||||
/// Failed to download the file.
|
/// Failed to download the file.
|
||||||
#[display("Unable to retrieve data from the specified URL: {_0}")]
|
#[display("Unable to retrieve data from the specified URL.")]
|
||||||
DownloadFailed(FetchError),
|
DownloadFailed,
|
||||||
|
|
||||||
/// The link leads to an outdated schedule.
|
/// The link leads to an outdated schedule.
|
||||||
///
|
///
|
||||||
@@ -119,7 +112,7 @@ mod schema {
|
|||||||
OutdatedSchedule,
|
OutdatedSchedule,
|
||||||
|
|
||||||
/// Failed to parse the schedule.
|
/// Failed to parse the schedule.
|
||||||
#[display("{_0}")]
|
#[display("{}", "_0.display()")]
|
||||||
InvalidSchedule(ParseError),
|
InvalidSchedule(ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +123,8 @@ mod schema {
|
|||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
ErrorCode::NonWhitelistedHost => serializer.serialize_str("NON_WHITELISTED_HOST"),
|
ErrorCode::NonWhitelistedHost => serializer.serialize_str("NON_WHITELISTED_HOST"),
|
||||||
ErrorCode::FetchFailed(_) => serializer.serialize_str("FETCH_FAILED"),
|
ErrorCode::FetchFailed => serializer.serialize_str("FETCH_FAILED"),
|
||||||
ErrorCode::DownloadFailed(_) => serializer.serialize_str("DOWNLOAD_FAILED"),
|
ErrorCode::DownloadFailed => serializer.serialize_str("DOWNLOAD_FAILED"),
|
||||||
ErrorCode::OutdatedSchedule => serializer.serialize_str("OUTDATED_SCHEDULE"),
|
ErrorCode::OutdatedSchedule => serializer.serialize_str("OUTDATED_SCHEDULE"),
|
||||||
ErrorCode::InvalidSchedule(_) => serializer.serialize_str("INVALID_SCHEDULE"),
|
ErrorCode::InvalidSchedule(_) => serializer.serialize_str("INVALID_SCHEDULE"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,18 +59,15 @@ async fn oauth(data: web::Json<Request>, app_state: web::Data<AppState>) -> Serv
|
|||||||
return ErrorCode::VkIdError.into_response();
|
return ErrorCode::VkIdError.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
match res.json::<VkIdAuthResponse>().await {
|
if let Ok(auth_data) = res.json::<VkIdAuthResponse>().await {
|
||||||
Ok(auth_data) =>
|
|
||||||
Ok(Response {
|
Ok(Response {
|
||||||
access_token: auth_data.id_token,
|
access_token: auth_data.id_token,
|
||||||
}).into(),
|
})
|
||||||
Err(error) => {
|
.into()
|
||||||
sentry::capture_error(&error);
|
} else {
|
||||||
|
|
||||||
ErrorCode::VkIdError.into_response()
|
ErrorCode::VkIdError.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(_) => ErrorCode::VkIdError.into_response(),
|
Err(_) => ErrorCode::VkIdError.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
use crate::xls_downloader::interface::{FetchError, FetchOk, FetchResult, XLSDownloader};
|
use crate::xls_downloader::interface::{FetchError, FetchOk, FetchResult, XLSDownloader};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use std::env;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct BasicXlsDownloader {
|
pub struct BasicXlsDownloader {
|
||||||
pub url: Option<String>,
|
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 client = reqwest::Client::new();
|
||||||
|
|
||||||
let response = if head {
|
let response = if head {
|
||||||
@@ -16,14 +13,14 @@ async fn fetch_specified(url: &String, user_agent: &String, head: bool) -> Fetch
|
|||||||
} else {
|
} else {
|
||||||
client.get(url)
|
client.get(url)
|
||||||
}
|
}
|
||||||
.header("User-Agent", user_agent.clone())
|
.header("User-Agent", user_agent)
|
||||||
.send()
|
.send()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
if r.status().as_u16() != 200 {
|
if r.status().as_u16() != 200 {
|
||||||
return Err(FetchError::BadStatusCode(r.status().as_u16()));
|
return Err(FetchError::BadStatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = r.headers();
|
let headers = r.headers();
|
||||||
@@ -33,18 +30,11 @@ async fn fetch_specified(url: &String, user_agent: &String, head: bool) -> Fetch
|
|||||||
let last_modified = headers.get("last-modified");
|
let last_modified = headers.get("last-modified");
|
||||||
let date = headers.get("date");
|
let date = headers.get("date");
|
||||||
|
|
||||||
if content_type.is_none() {
|
if content_type.is_none() || etag.is_none() || last_modified.is_none() || date.is_none()
|
||||||
Err(FetchError::BadHeaders("Content-Type".to_string()))
|
{
|
||||||
} else if etag.is_none() {
|
Err(FetchError::BadHeaders)
|
||||||
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" {
|
} else if content_type.unwrap() != "application/vnd.ms-excel" {
|
||||||
Err(FetchError::BadContentType(
|
Err(FetchError::BadContentType)
|
||||||
content_type.unwrap().to_str().unwrap().to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
let etag = etag.unwrap().to_str().unwrap().to_string();
|
let etag = etag.unwrap().to_str().unwrap().to_string();
|
||||||
let last_modified =
|
let last_modified =
|
||||||
@@ -59,16 +49,13 @@ async fn fetch_specified(url: &String, user_agent: &String, head: bool) -> Fetch
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(error) => Err(FetchError::Unknown(Arc::new(error))),
|
Err(_) => Err(FetchError::Unknown),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicXlsDownloader {
|
impl BasicXlsDownloader {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
BasicXlsDownloader {
|
BasicXlsDownloader { url: None }
|
||||||
url: None,
|
|
||||||
user_agent: env::var("REQWEST_USER_AGENT").expect("USER_AGENT must be set"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,12 +64,17 @@ impl XLSDownloader for BasicXlsDownloader {
|
|||||||
if self.url.is_none() {
|
if self.url.is_none() {
|
||||||
Err(FetchError::NoUrlProvided)
|
Err(FetchError::NoUrlProvided)
|
||||||
} else {
|
} else {
|
||||||
fetch_specified(self.url.as_ref().unwrap(), &self.user_agent, head).await
|
fetch_specified(
|
||||||
|
self.url.as_ref().unwrap(),
|
||||||
|
"t.me/polytechnic_next".to_string(),
|
||||||
|
head,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_url(&mut self, url: String) -> FetchResult {
|
async fn set_url(&mut self, url: String) -> FetchResult {
|
||||||
let result = fetch_specified(&url, &self.user_agent, true).await;
|
let result = fetch_specified(&url, "t.me/polytechnic_next".to_string(), true).await;
|
||||||
|
|
||||||
if let Ok(_) = result {
|
if let Ok(_) = result {
|
||||||
self.url = Some(url);
|
self.url = Some(url);
|
||||||
@@ -94,7 +86,7 @@ impl XLSDownloader for BasicXlsDownloader {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::xls_downloader::basic_impl::{fetch_specified, BasicXlsDownloader};
|
use crate::xls_downloader::basic_impl::{BasicXlsDownloader, fetch_specified};
|
||||||
use crate::xls_downloader::interface::{FetchError, XLSDownloader};
|
use crate::xls_downloader::interface::{FetchError, XLSDownloader};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -103,8 +95,8 @@ mod tests {
|
|||||||
let user_agent = String::new();
|
let user_agent = String::new();
|
||||||
|
|
||||||
let results = [
|
let results = [
|
||||||
fetch_specified(&url, &user_agent, true).await,
|
fetch_specified(&url, user_agent.clone(), true).await,
|
||||||
fetch_specified(&url, &user_agent, false).await,
|
fetch_specified(&url, user_agent.clone(), false).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert!(results[0].is_err());
|
assert!(results[0].is_err());
|
||||||
@@ -117,17 +109,21 @@ mod tests {
|
|||||||
let user_agent = String::new();
|
let user_agent = String::new();
|
||||||
|
|
||||||
let results = [
|
let results = [
|
||||||
fetch_specified(&url, &user_agent, true).await,
|
fetch_specified(&url, user_agent.clone(), true).await,
|
||||||
fetch_specified(&url, &user_agent, false).await,
|
fetch_specified(&url, user_agent.clone(), false).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert!(results[0].is_err());
|
assert!(results[0].is_err());
|
||||||
assert!(results[1].is_err());
|
assert!(results[1].is_err());
|
||||||
|
|
||||||
let expected_error = FetchError::BadStatusCode(404);
|
assert_eq!(
|
||||||
|
*results[0].as_ref().err().unwrap(),
|
||||||
assert_eq!(*results[0].as_ref().err().unwrap(), expected_error);
|
FetchError::BadStatusCode
|
||||||
assert_eq!(*results[1].as_ref().err().unwrap(), expected_error);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*results[1].as_ref().err().unwrap(),
|
||||||
|
FetchError::BadStatusCode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -136,17 +132,15 @@ mod tests {
|
|||||||
let user_agent = String::new();
|
let user_agent = String::new();
|
||||||
|
|
||||||
let results = [
|
let results = [
|
||||||
fetch_specified(&url, &user_agent, true).await,
|
fetch_specified(&url, user_agent.clone(), true).await,
|
||||||
fetch_specified(&url, &user_agent, false).await,
|
fetch_specified(&url, user_agent.clone(), false).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert!(results[0].is_err());
|
assert!(results[0].is_err());
|
||||||
assert!(results[1].is_err());
|
assert!(results[1].is_err());
|
||||||
|
|
||||||
let expected_error = FetchError::BadHeaders("ETag".to_string());
|
assert_eq!(*results[0].as_ref().err().unwrap(), FetchError::BadHeaders);
|
||||||
|
assert_eq!(*results[1].as_ref().err().unwrap(), FetchError::BadHeaders);
|
||||||
assert_eq!(*results[0].as_ref().err().unwrap(), expected_error);
|
|
||||||
assert_eq!(*results[1].as_ref().err().unwrap(), expected_error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -155,12 +149,21 @@ mod tests {
|
|||||||
let user_agent = String::new();
|
let user_agent = String::new();
|
||||||
|
|
||||||
let results = [
|
let results = [
|
||||||
fetch_specified(&url, &user_agent, true).await,
|
fetch_specified(&url, user_agent.clone(), true).await,
|
||||||
fetch_specified(&url, &user_agent, false).await,
|
fetch_specified(&url, user_agent.clone(), false).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert!(results[0].is_err());
|
assert!(results[0].is_err());
|
||||||
assert!(results[1].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]
|
#[tokio::test]
|
||||||
@@ -169,8 +172,8 @@ mod tests {
|
|||||||
let user_agent = String::new();
|
let user_agent = String::new();
|
||||||
|
|
||||||
let results = [
|
let results = [
|
||||||
fetch_specified(&url, &user_agent, true).await,
|
fetch_specified(&url, user_agent.clone(), true).await,
|
||||||
fetch_specified(&url, &user_agent, false).await,
|
fetch_specified(&url, user_agent.clone(), false).await,
|
||||||
];
|
];
|
||||||
|
|
||||||
assert!(results[0].is_ok());
|
assert!(results[0].is_ok());
|
||||||
|
|||||||
@@ -1,38 +1,22 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use derive_more::Display;
|
|
||||||
use std::mem::discriminant;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use utoipa::ToSchema;
|
|
||||||
|
|
||||||
/// XLS data retrieval errors.
|
/// XLS data retrieval errors.
|
||||||
#[derive(Clone, Debug, ToSchema, Display)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum FetchError {
|
pub enum FetchError {
|
||||||
/// File url is not set.
|
/// File url is not set.
|
||||||
#[display("The link to the timetable was not provided earlier.")]
|
|
||||||
NoUrlProvided,
|
NoUrlProvided,
|
||||||
|
|
||||||
/// Unknown error.
|
/// Unknown error.
|
||||||
#[display("An unknown error occurred while downloading the file.")]
|
Unknown,
|
||||||
#[schema(value_type = String)]
|
|
||||||
Unknown(Arc<reqwest::Error>),
|
|
||||||
|
|
||||||
/// Server returned a status code different from 200.
|
/// Server returned a status code different from 200.
|
||||||
#[display("Server returned a status code {_0}.")]
|
BadStatusCode,
|
||||||
BadStatusCode(u16),
|
|
||||||
|
|
||||||
/// The url leads to a file of a different type.
|
/// The url leads to a file of a different type.
|
||||||
#[display("The link leads to a file of type '{_0}'.")]
|
BadContentType,
|
||||||
BadContentType(String),
|
|
||||||
|
|
||||||
/// Server doesn't return expected headers.
|
/// Server doesn't return expected headers.
|
||||||
#[display("Server doesn't return expected header(s) '{_0}'.")]
|
BadHeaders,
|
||||||
BadHeaders(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for FetchError {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
discriminant(self) == discriminant(other)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of XLS data retrieval.
|
/// Result of XLS data retrieval.
|
||||||
|
|||||||
Reference in New Issue
Block a user