From 3e90e2fa9f779c7899fbfac18f9b65e82a33e971 Mon Sep 17 00:00:00 2001 From: Tsuki Date: Fri, 8 Aug 2025 07:20:28 +0800 Subject: [PATCH] sync --- .env | 5 +- .github/workflows/docker.yml | 61 +++++++++ ...8ef517de4d3d3445a96f901fd84ab3df2f9c.json} | 10 +- ...70d59604ee99538081add65cc92c2ec74720e.json | 58 +++++++++ ...02dad038c2facb9cb1d566fe7572d659c0a03.json | 20 --- ...52b1925aa726d3601f79715ed7e41788fbd9.json} | 10 +- ...96277f498da55d10c5d640dd544f26ae301c.json} | 16 ++- ...9194d1965d2876676780275ecdb45ee9e16f5.json | 20 --- ...3ad40844efb5778b342271fd2047492cef22.json} | 10 +- ...a52aed5618d687519727c6a39fd7bb77c9237.json | 44 +++++++ ...8d0a7f83a74433a42d7a31c71a97c522aed79.json | 65 ---------- ...babe383e1d7ac92367fa24128db3b0d153f2.json} | 16 ++- ...b4ff715f5feb76fe20e7f9abafcc390e43c2.json} | 16 ++- ...eff7e70cf981804cecd228b6477885e1386ae.json | 67 ---------- ...f8165c800abf81f4ab74f4a65f6339637ae5.json} | 11 +- ...4f35867068c68ebed4b326517631a8e978da5.json | 20 --- ...334dca8d7209fa914355a38287af587ae8d4c.json | 20 --- Cargo.lock | 119 ++++++++++++++++-- Cargo.toml | 1 + Dockerfile | 49 ++++++++ src/app.rs | 24 +++- src/graphql/subscription.rs | 7 +- src/listener/mod.rs | 2 +- src/services/mod.rs | 1 + src/services/mosaic_service.rs | 65 ++++++++++ 25 files changed, 475 insertions(+), 262 deletions(-) create mode 100644 .github/workflows/docker.yml rename .sqlx/{query-353dee803eee3a4f1e68a8e5f391879128dfc59b963bd519e8dfb2fae24c6c3b.json => query-19edd626ddc3d5053f2f0c39a7f98ef517de4d3d3445a96f901fd84ab3df2f9c.json} (63%) create mode 100644 .sqlx/query-2d227347f45a681968fcee4452e70d59604ee99538081add65cc92c2ec74720e.json delete mode 100644 .sqlx/query-41e05b2ce23645a612d385f0faa02dad038c2facb9cb1d566fe7572d659c0a03.json rename .sqlx/{query-6e1bf3857d6eb4df7f14d0eca0823f39c50f1b18e61bc469d616598a20c5bb91.json => query-567dd7f85208ee5cfc91eb5f86a752b1925aa726d3601f79715ed7e41788fbd9.json} (61%) rename .sqlx/{query-2ab074bca985746d32cb5d932c0012019486847c80eb12d5fccda6841a22613d.json => query-56c51721fae7860a57aadbf46acd96277f498da55d10c5d640dd544f26ae301c.json} (64%) delete mode 100644 .sqlx/query-56c6cd9cd434821ad87afdc1e0e9194d1965d2876676780275ecdb45ee9e16f5.json rename .sqlx/{query-0325b8932e0a71f18e0018185b5559eb6ec748c67417efaf62520a690ca2b385.json => query-669fc31e8b8eea3f51f5f0deb6fa3ad40844efb5778b342271fd2047492cef22.json} (63%) create mode 100644 .sqlx/query-9dff45451b7fe2804768c72642ba52aed5618d687519727c6a39fd7bb77c9237.json delete mode 100644 .sqlx/query-a27168afd0caa5f71fa1036e2508d0a7f83a74433a42d7a31c71a97c522aed79.json rename .sqlx/{query-84af2d99fe5036ae0f0041aa539427312e89b9faba1c8b6900164fbd0f7a703e.json => query-abca093e4f8ee335527fa29fcc79babe383e1d7ac92367fa24128db3b0d153f2.json} (67%) rename .sqlx/{query-3bdc7146e0751bcb04a3ed6831c0d094a5d4b38937542f5f1529748c8c6b3aa6.json => query-ba6430e67988cda88df40d61f0a9b4ff715f5feb76fe20e7f9abafcc390e43c2.json} (64%) delete mode 100644 .sqlx/query-c2c999a6ab3b058deced5568868eff7e70cf981804cecd228b6477885e1386ae.json rename .sqlx/{query-8a1b2c956e1ff38b2a2a6179f5f7aaddd80dfef77ed05694ba17d7dedebac9dc.json => query-d3f1440480f440bbc8577adfbebbf8165c800abf81f4ab74f4a65f6339637ae5.json} (66%) delete mode 100644 .sqlx/query-d470eafcaff0b536c38e0d29a1a4f35867068c68ebed4b326517631a8e978da5.json delete mode 100644 .sqlx/query-d718520b0f79dd1b0999cbb65bd334dca8d7209fa914355a38287af587ae8d4c.json create mode 100644 Dockerfile create mode 100644 src/services/mosaic_service.rs diff --git a/.env b/.env index c605cb1..86e2ebf 100644 --- a/.env +++ b/.env @@ -2,4 +2,7 @@ DATABASE_URL=postgresql://mmap:yjhcfzXWrzslzl1331@8.217.64.157:5433/mmap JWT_SECRET="JvGpWgGWLHAhvhxN7BuOVtUWfMXm6xAqjClaTwOcAnI=" RUST_LOG=debug PORT=3050 -TILE_SERVER="http://127.0.0.1:3060/api" \ No newline at end of file +TILE_SERVER="http://47.95.11.22:3060/api" +KAFKA_BROKERS="8.217.64.157:9094" +KAFKA_TOPIC=data-output +KAFKA_GROUP_ID=mapp \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..ee25249 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,61 @@ +name: Docker Build and Push + +on: + push: + branches: [ main, master ] + tags: [ 'v*' ] + pull_request: + branches: [ main, master ] + +env: + REGISTRY: crpi-8rsu3rjoi0n0hc4m.cn-hongkong.personal.cr.aliyuncs.com + IMAGE_NAME: tmmapp/tiler + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.ALIYUN_USERNAME }} + password: ${{ secrets.ALIYUN_PASSWORD }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix={{branch}}- + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Output image digest + if: github.event_name != 'pull_request' + run: echo ${{ steps.build.outputs.digest }} \ No newline at end of file diff --git a/.sqlx/query-353dee803eee3a4f1e68a8e5f391879128dfc59b963bd519e8dfb2fae24c6c3b.json b/.sqlx/query-19edd626ddc3d5053f2f0c39a7f98ef517de4d3d3445a96f901fd84ab3df2f9c.json similarity index 63% rename from .sqlx/query-353dee803eee3a4f1e68a8e5f391879128dfc59b963bd519e8dfb2fae24c6c3b.json rename to .sqlx/query-19edd626ddc3d5053f2f0c39a7f98ef517de4d3d3445a96f901fd84ab3df2f9c.json index 8ec9925..c154e8a 100644 --- a/.sqlx/query-353dee803eee3a4f1e68a8e5f391879128dfc59b963bd519e8dfb2fae24c6c3b.json +++ b/.sqlx/query-19edd626ddc3d5053f2f0c39a7f98ef517de4d3d3445a96f901fd84ab3df2f9c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, code, created_by, used_by, is_used, expires_at, created_at, used_at\n FROM invite_codes \n WHERE code = $1\n ", + "query": "\n SELECT id, code, created_by, used_by, is_used, \n expires_at as \"expires_at: chrono::DateTime\", \n created_at as \"created_at: chrono::DateTime\", \n used_at as \"used_at: chrono::DateTime\"\n FROM invite_codes \n WHERE code = $1\n ", "describe": { "columns": [ { @@ -30,17 +30,17 @@ }, { "ordinal": 5, - "name": "expires_at", + "name": "expires_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 6, - "name": "created_at", + "name": "created_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 7, - "name": "used_at", + "name": "used_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -60,5 +60,5 @@ true ] }, - "hash": "353dee803eee3a4f1e68a8e5f391879128dfc59b963bd519e8dfb2fae24c6c3b" + "hash": "19edd626ddc3d5053f2f0c39a7f98ef517de4d3d3445a96f901fd84ab3df2f9c" } diff --git a/.sqlx/query-2d227347f45a681968fcee4452e70d59604ee99538081add65cc92c2ec74720e.json b/.sqlx/query-2d227347f45a681968fcee4452e70d59604ee99538081add65cc92c2ec74720e.json new file mode 100644 index 0000000..e90dbf7 --- /dev/null +++ b/.sqlx/query-2d227347f45a681968fcee4452e70d59604ee99538081add65cc92c2ec74720e.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n id,\n ingestion_time as \"ingestion_time: chrono::DateTime\",\n data_time as \"data_time: chrono::DateTime\",\n source,\n storage_url,\n created_at as \"created_at: chrono::DateTime\",\n updated_at as \"updated_at: chrono::DateTime\"\n FROM data_ingestion \n WHERE source = $1\n ORDER BY data_time DESC \n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "ingestion_time: chrono::DateTime", + "type_info": "Timestamp" + }, + { + "ordinal": 2, + "name": "data_time: chrono::DateTime", + "type_info": "Timestamp" + }, + { + "ordinal": 3, + "name": "source", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "storage_url", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "created_at: chrono::DateTime", + "type_info": "Timestamp" + }, + { + "ordinal": 6, + "name": "updated_at: chrono::DateTime", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "2d227347f45a681968fcee4452e70d59604ee99538081add65cc92c2ec74720e" +} diff --git a/.sqlx/query-41e05b2ce23645a612d385f0faa02dad038c2facb9cb1d566fe7572d659c0a03.json b/.sqlx/query-41e05b2ce23645a612d385f0faa02dad038c2facb9cb1d566fe7572d659c0a03.json deleted file mode 100644 index 7901b81..0000000 --- a/.sqlx/query-41e05b2ce23645a612d385f0faa02dad038c2facb9cb1d566fe7572d659c0a03.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM users WHERE is_activate = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "41e05b2ce23645a612d385f0faa02dad038c2facb9cb1d566fe7572d659c0a03" -} diff --git a/.sqlx/query-6e1bf3857d6eb4df7f14d0eca0823f39c50f1b18e61bc469d616598a20c5bb91.json b/.sqlx/query-567dd7f85208ee5cfc91eb5f86a752b1925aa726d3601f79715ed7e41788fbd9.json similarity index 61% rename from .sqlx/query-6e1bf3857d6eb4df7f14d0eca0823f39c50f1b18e61bc469d616598a20c5bb91.json rename to .sqlx/query-567dd7f85208ee5cfc91eb5f86a752b1925aa726d3601f79715ed7e41788fbd9.json index 4ebaa61..9574ddc 100644 --- a/.sqlx/query-6e1bf3857d6eb4df7f14d0eca0823f39c50f1b18e61bc469d616598a20c5bb91.json +++ b/.sqlx/query-567dd7f85208ee5cfc91eb5f86a752b1925aa726d3601f79715ed7e41788fbd9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, code, created_by, used_by, is_used, expires_at, created_at, used_at\n FROM invite_codes \n WHERE created_by = $1\n ORDER BY created_at DESC\n ", + "query": "\n SELECT id, code, created_by, used_by, is_used, \n expires_at as \"expires_at: chrono::DateTime\", \n created_at as \"created_at: chrono::DateTime\", \n used_at as \"used_at: chrono::DateTime\"\n FROM invite_codes \n WHERE created_by = $1\n ORDER BY created_at DESC\n ", "describe": { "columns": [ { @@ -30,17 +30,17 @@ }, { "ordinal": 5, - "name": "expires_at", + "name": "expires_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 6, - "name": "created_at", + "name": "created_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 7, - "name": "used_at", + "name": "used_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -60,5 +60,5 @@ true ] }, - "hash": "6e1bf3857d6eb4df7f14d0eca0823f39c50f1b18e61bc469d616598a20c5bb91" + "hash": "567dd7f85208ee5cfc91eb5f86a752b1925aa726d3601f79715ed7e41788fbd9" } diff --git a/.sqlx/query-2ab074bca985746d32cb5d932c0012019486847c80eb12d5fccda6841a22613d.json b/.sqlx/query-56c51721fae7860a57aadbf46acd96277f498da55d10c5d640dd544f26ae301c.json similarity index 64% rename from .sqlx/query-2ab074bca985746d32cb5d932c0012019486847c80eb12d5fccda6841a22613d.json rename to .sqlx/query-56c51721fae7860a57aadbf46acd96277f498da55d10c5d640dd544f26ae301c.json index 952089d..fc607bb 100644 --- a/.sqlx/query-2ab074bca985746d32cb5d932c0012019486847c80eb12d5fccda6841a22613d.json +++ b/.sqlx/query-56c51721fae7860a57aadbf46acd96277f498da55d10c5d640dd544f26ae301c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, username, email, password_hash, role as \"role: Role\", invite_code_id, created_at, updated_at\n FROM users WHERE username = $1\n ", + "query": "\n SELECT id, username, email, password_hash, role as \"role: Role\", invite_code_id, is_activate, \n created_at as \"created_at: chrono::DateTime\", \n updated_at as \"updated_at: chrono::DateTime\"\n FROM users WHERE username = $1\n ", "describe": { "columns": [ { @@ -35,12 +35,17 @@ }, { "ordinal": 6, - "name": "created_at", - "type_info": "Timestamptz" + "name": "is_activate", + "type_info": "Bool" }, { "ordinal": 7, - "name": "updated_at", + "name": "created_at: chrono::DateTime", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "updated_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -56,9 +61,10 @@ false, false, true, + false, true, true ] }, - "hash": "2ab074bca985746d32cb5d932c0012019486847c80eb12d5fccda6841a22613d" + "hash": "56c51721fae7860a57aadbf46acd96277f498da55d10c5d640dd544f26ae301c" } diff --git a/.sqlx/query-56c6cd9cd434821ad87afdc1e0e9194d1965d2876676780275ecdb45ee9e16f5.json b/.sqlx/query-56c6cd9cd434821ad87afdc1e0e9194d1965d2876676780275ecdb45ee9e16f5.json deleted file mode 100644 index 1643473..0000000 --- a/.sqlx/query-56c6cd9cd434821ad87afdc1e0e9194d1965d2876676780275ecdb45ee9e16f5.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM users WHERE role = 'user'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "56c6cd9cd434821ad87afdc1e0e9194d1965d2876676780275ecdb45ee9e16f5" -} diff --git a/.sqlx/query-0325b8932e0a71f18e0018185b5559eb6ec748c67417efaf62520a690ca2b385.json b/.sqlx/query-669fc31e8b8eea3f51f5f0deb6fa3ad40844efb5778b342271fd2047492cef22.json similarity index 63% rename from .sqlx/query-0325b8932e0a71f18e0018185b5559eb6ec748c67417efaf62520a690ca2b385.json rename to .sqlx/query-669fc31e8b8eea3f51f5f0deb6fa3ad40844efb5778b342271fd2047492cef22.json index 9ee8168..dcc8a75 100644 --- a/.sqlx/query-0325b8932e0a71f18e0018185b5559eb6ec748c67417efaf62520a690ca2b385.json +++ b/.sqlx/query-669fc31e8b8eea3f51f5f0deb6fa3ad40844efb5778b342271fd2047492cef22.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, code, created_by, used_by, is_used, expires_at, created_at, used_at\n FROM invite_codes \n WHERE id = $1\n ", + "query": "\n SELECT id, code, created_by, used_by, is_used, \n expires_at as \"expires_at: chrono::DateTime\", \n created_at as \"created_at: chrono::DateTime\", \n used_at as \"used_at: chrono::DateTime\"\n FROM invite_codes \n WHERE id = $1\n ", "describe": { "columns": [ { @@ -30,17 +30,17 @@ }, { "ordinal": 5, - "name": "expires_at", + "name": "expires_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 6, - "name": "created_at", + "name": "created_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 7, - "name": "used_at", + "name": "used_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -60,5 +60,5 @@ true ] }, - "hash": "0325b8932e0a71f18e0018185b5559eb6ec748c67417efaf62520a690ca2b385" + "hash": "669fc31e8b8eea3f51f5f0deb6fa3ad40844efb5778b342271fd2047492cef22" } diff --git a/.sqlx/query-9dff45451b7fe2804768c72642ba52aed5618d687519727c6a39fd7bb77c9237.json b/.sqlx/query-9dff45451b7fe2804768c72642ba52aed5618d687519727c6a39fd7bb77c9237.json new file mode 100644 index 0000000..ff7899f --- /dev/null +++ b/.sqlx/query-9dff45451b7fe2804768c72642ba52aed5618d687519727c6a39fd7bb77c9237.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n COUNT(*) as total_users,\n COUNT(CASE WHEN is_activate = true THEN 1 END) as total_active_users,\n COUNT(CASE WHEN is_activate = false THEN 1 END) as total_inactive_users,\n COUNT(CASE WHEN role = 'Admin' THEN 1 END) as total_admin_users,\n COUNT(CASE WHEN role = 'User' THEN 1 END) as total_user_users\n FROM users\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "total_users", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "total_active_users", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "total_inactive_users", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "total_admin_users", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "total_user_users", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null, + null, + null, + null, + null + ] + }, + "hash": "9dff45451b7fe2804768c72642ba52aed5618d687519727c6a39fd7bb77c9237" +} diff --git a/.sqlx/query-a27168afd0caa5f71fa1036e2508d0a7f83a74433a42d7a31c71a97c522aed79.json b/.sqlx/query-a27168afd0caa5f71fa1036e2508d0a7f83a74433a42d7a31c71a97c522aed79.json deleted file mode 100644 index 731e9ed..0000000 --- a/.sqlx/query-a27168afd0caa5f71fa1036e2508d0a7f83a74433a42d7a31c71a97c522aed79.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, username, email, password_hash, role as \"role: Role\", invite_code_id, created_at, updated_at FROM users LIMIT $1 OFFSET $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "role: Role", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "invite_code_id", - "type_info": "Uuid" - }, - { - "ordinal": 6, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 7, - "name": "updated_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - true, - true - ] - }, - "hash": "a27168afd0caa5f71fa1036e2508d0a7f83a74433a42d7a31c71a97c522aed79" -} diff --git a/.sqlx/query-84af2d99fe5036ae0f0041aa539427312e89b9faba1c8b6900164fbd0f7a703e.json b/.sqlx/query-abca093e4f8ee335527fa29fcc79babe383e1d7ac92367fa24128db3b0d153f2.json similarity index 67% rename from .sqlx/query-84af2d99fe5036ae0f0041aa539427312e89b9faba1c8b6900164fbd0f7a703e.json rename to .sqlx/query-abca093e4f8ee335527fa29fcc79babe383e1d7ac92367fa24128db3b0d153f2.json index b1ae72d..80d94f7 100644 --- a/.sqlx/query-84af2d99fe5036ae0f0041aa539427312e89b9faba1c8b6900164fbd0f7a703e.json +++ b/.sqlx/query-abca093e4f8ee335527fa29fcc79babe383e1d7ac92367fa24128db3b0d153f2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE users SET invite_code_id = $1 WHERE id = $2\n RETURNING id, username, email, password_hash, role as \"role: Role\", invite_code_id, created_at, updated_at\n ", + "query": "\n UPDATE users SET invite_code_id = $1 WHERE id = $2\n RETURNING id, username, email, password_hash, role as \"role: Role\", invite_code_id, is_activate, \n created_at as \"created_at: chrono::DateTime\", \n updated_at as \"updated_at: chrono::DateTime\"\n ", "describe": { "columns": [ { @@ -35,12 +35,17 @@ }, { "ordinal": 6, - "name": "created_at", - "type_info": "Timestamptz" + "name": "is_activate", + "type_info": "Bool" }, { "ordinal": 7, - "name": "updated_at", + "name": "created_at: chrono::DateTime", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "updated_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -57,9 +62,10 @@ false, false, true, + false, true, true ] }, - "hash": "84af2d99fe5036ae0f0041aa539427312e89b9faba1c8b6900164fbd0f7a703e" + "hash": "abca093e4f8ee335527fa29fcc79babe383e1d7ac92367fa24128db3b0d153f2" } diff --git a/.sqlx/query-3bdc7146e0751bcb04a3ed6831c0d094a5d4b38937542f5f1529748c8c6b3aa6.json b/.sqlx/query-ba6430e67988cda88df40d61f0a9b4ff715f5feb76fe20e7f9abafcc390e43c2.json similarity index 64% rename from .sqlx/query-3bdc7146e0751bcb04a3ed6831c0d094a5d4b38937542f5f1529748c8c6b3aa6.json rename to .sqlx/query-ba6430e67988cda88df40d61f0a9b4ff715f5feb76fe20e7f9abafcc390e43c2.json index f1b8a37..5e646a3 100644 --- a/.sqlx/query-3bdc7146e0751bcb04a3ed6831c0d094a5d4b38937542f5f1529748c8c6b3aa6.json +++ b/.sqlx/query-ba6430e67988cda88df40d61f0a9b4ff715f5feb76fe20e7f9abafcc390e43c2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, username, email, password_hash, role as \"role: Role\", invite_code_id, created_at, updated_at\n FROM users WHERE id = $1\n ", + "query": "\n SELECT id, username, email, password_hash, role as \"role: Role\", invite_code_id, is_activate, \n created_at as \"created_at: chrono::DateTime\", \n updated_at as \"updated_at: chrono::DateTime\"\n FROM users WHERE id = $1\n ", "describe": { "columns": [ { @@ -35,12 +35,17 @@ }, { "ordinal": 6, - "name": "created_at", - "type_info": "Timestamptz" + "name": "is_activate", + "type_info": "Bool" }, { "ordinal": 7, - "name": "updated_at", + "name": "created_at: chrono::DateTime", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "updated_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -56,9 +61,10 @@ false, false, true, + false, true, true ] }, - "hash": "3bdc7146e0751bcb04a3ed6831c0d094a5d4b38937542f5f1529748c8c6b3aa6" + "hash": "ba6430e67988cda88df40d61f0a9b4ff715f5feb76fe20e7f9abafcc390e43c2" } diff --git a/.sqlx/query-c2c999a6ab3b058deced5568868eff7e70cf981804cecd228b6477885e1386ae.json b/.sqlx/query-c2c999a6ab3b058deced5568868eff7e70cf981804cecd228b6477885e1386ae.json deleted file mode 100644 index bc1ee7c..0000000 --- a/.sqlx/query-c2c999a6ab3b058deced5568868eff7e70cf981804cecd228b6477885e1386ae.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO users (username, email, password_hash, role)\n VALUES ($1, $2, $3, $4)\n RETURNING id, username, email, password_hash, role as \"role: Role\", invite_code_id, created_at, updated_at\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "role: Role", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "invite_code_id", - "type_info": "Uuid" - }, - { - "ordinal": 6, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 7, - "name": "updated_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Varchar" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - true, - true - ] - }, - "hash": "c2c999a6ab3b058deced5568868eff7e70cf981804cecd228b6477885e1386ae" -} diff --git a/.sqlx/query-8a1b2c956e1ff38b2a2a6179f5f7aaddd80dfef77ed05694ba17d7dedebac9dc.json b/.sqlx/query-d3f1440480f440bbc8577adfbebbf8165c800abf81f4ab74f4a65f6339637ae5.json similarity index 66% rename from .sqlx/query-8a1b2c956e1ff38b2a2a6179f5f7aaddd80dfef77ed05694ba17d7dedebac9dc.json rename to .sqlx/query-d3f1440480f440bbc8577adfbebbf8165c800abf81f4ab74f4a65f6339637ae5.json index a6d2689..8a925c6 100644 --- a/.sqlx/query-8a1b2c956e1ff38b2a2a6179f5f7aaddd80dfef77ed05694ba17d7dedebac9dc.json +++ b/.sqlx/query-d3f1440480f440bbc8577adfbebbf8165c800abf81f4ab74f4a65f6339637ae5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO users (username, email, password_hash, role)\n VALUES ($1, $2, $3, $4)\n RETURNING id, username, email, password_hash, role as \"role: Role\", invite_code_id, is_activate, created_at, updated_at\n ", + "query": "\n INSERT INTO users (username, email, password_hash, role, is_activate)\n VALUES ($1, $2, $3, $4, $5)\n RETURNING id, username, email, password_hash, role as \"role: Role\", invite_code_id, is_activate, \n created_at as \"created_at: chrono::DateTime\", \n updated_at as \"updated_at: chrono::DateTime\"\n ", "describe": { "columns": [ { @@ -40,12 +40,12 @@ }, { "ordinal": 7, - "name": "created_at", + "name": "created_at: chrono::DateTime", "type_info": "Timestamptz" }, { "ordinal": 8, - "name": "updated_at", + "name": "updated_at: chrono::DateTime", "type_info": "Timestamptz" } ], @@ -54,7 +54,8 @@ "Varchar", "Varchar", "Varchar", - "Varchar" + "Varchar", + "Bool" ] }, "nullable": [ @@ -69,5 +70,5 @@ true ] }, - "hash": "8a1b2c956e1ff38b2a2a6179f5f7aaddd80dfef77ed05694ba17d7dedebac9dc" + "hash": "d3f1440480f440bbc8577adfbebbf8165c800abf81f4ab74f4a65f6339637ae5" } diff --git a/.sqlx/query-d470eafcaff0b536c38e0d29a1a4f35867068c68ebed4b326517631a8e978da5.json b/.sqlx/query-d470eafcaff0b536c38e0d29a1a4f35867068c68ebed4b326517631a8e978da5.json deleted file mode 100644 index 90cf407..0000000 --- a/.sqlx/query-d470eafcaff0b536c38e0d29a1a4f35867068c68ebed4b326517631a8e978da5.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM users WHERE is_activate = true", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "d470eafcaff0b536c38e0d29a1a4f35867068c68ebed4b326517631a8e978da5" -} diff --git a/.sqlx/query-d718520b0f79dd1b0999cbb65bd334dca8d7209fa914355a38287af587ae8d4c.json b/.sqlx/query-d718520b0f79dd1b0999cbb65bd334dca8d7209fa914355a38287af587ae8d4c.json deleted file mode 100644 index bd5b3ef..0000000 --- a/.sqlx/query-d718520b0f79dd1b0999cbb65bd334dca8d7209fa914355a38287af587ae8d4c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT COUNT(*) FROM users WHERE role = 'admin'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "count", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "d718520b0f79dd1b0999cbb65bd334dca8d7209fa914355a38287af587ae8d4c" -} diff --git a/Cargo.lock b/Cargo.lock index 96d5cf3..102694f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -236,7 +249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8725874ecfbf399e071150b8619c4071d7b2b7a2f117e173dddef53c6bdb6bb1" dependencies = [ "async-graphql", - "axum", + "axum 0.8.4", "bytes", "futures-util", "serde_json", @@ -454,13 +467,42 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "axum" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "axum-core", + "axum-core 0.5.2", "axum-macros", "base64 0.22.1", "bytes", @@ -472,7 +514,7 @@ dependencies = [ "hyper", "hyper-util", "itoa", - "matchit", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -492,6 +534,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.5.2" @@ -518,8 +580,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" dependencies = [ - "axum", - "axum-core", + "axum 0.8.4", + "axum-core 0.5.2", "bytes", "futures-util", "headers", @@ -542,7 +604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418f97e310fb4e9bc5c38948ede899a2aa00911529aaf69ff38c454f4a653397" dependencies = [ "async-trait", - "axum", + "axum 0.8.4", "axum-extra", "dashmap", "derive_builder", @@ -572,7 +634,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84bb2eec9f1a2c150ce1204d843d690db0da305f6f2848cbfd4a840c830b4f0b" dependencies = [ - "axum", + "axum 0.8.4", "base64 0.21.7", "bytes", "futures-util", @@ -592,6 +654,23 @@ dependencies = [ "url", ] +[[package]] +name = "axum_gcra" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b171b8669e7a83349c726cc857529da933b03c503ad64f6258e546e0ec924ca5" +dependencies = [ + "ahash 0.8.12", + "async-trait", + "axum 0.7.9", + "futures-util", + "http", + "pin-project-lite", + "scc", + "tokio", + "tower 0.4.13", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -1518,7 +1597,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", ] [[package]] @@ -2154,9 +2233,10 @@ dependencies = [ "async-graphql", "async-graphql-axum", "async-stream", - "axum", + "axum 0.8.4", "axum-jwt-auth", "axum-reverse-proxy", + "axum_gcra", "chrono", "clap", "dotenvy", @@ -2177,6 +2257,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matchit" version = "0.8.4" @@ -3173,6 +3259,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scc" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.27" @@ -3188,6 +3283,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "sea-query" version = "0.32.6" diff --git a/Cargo.toml b/Cargo.toml index 9a65f11..eab0a8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,4 +48,5 @@ axum-reverse-proxy = "1.0.3" rustls = { version = "0.23", features = ["aws-lc-rs"] } clap = { version = "4.0", features = ["derive"] } rdkafka = "0.38.0" +axum_gcra = "0.1.1" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..23354a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +FROM rust:1.88.0-slim as builder + +WORKDIR /usr/src/app + +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + libpq-dev \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +RUN pkg-config --version && \ + pkg-config --libs openssl && \ + pkg-config --cflags openssl + +COPY Cargo.toml Cargo.lock ./ +COPY src ./src + +COPY .sqlx .sqlx + +ENV SQLX_OFFLINE=true +RUN cargo build --release + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + libpq-dev \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +RUN pkg-config --version && \ + pkg-config --libs openssl && \ + pkg-config --cflags openssl + +RUN groupadd -r appuser && useradd -r -g appuser appuser + +WORKDIR /app + +COPY --from=builder /usr/src/app/target/release/mapp-tile /app/mapp-tile + +RUN chown -R appuser:appuser /app +USER appuser + +EXPOSE 3060 + +# 运行应用程序 +CMD ["./mapp-tile"] \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 88d1e7b..f8bc9c2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,6 @@ +use axum_gcra::{gcra::Quota, real_ip::RealIp, RateLimitLayer}; use axum_reverse_proxy::ReverseProxy; -use std::sync::Arc; +use std::{num::NonZero, sync::Arc, time::Duration}; use tokio::sync::broadcast; use async_graphql::{ @@ -9,6 +10,7 @@ use async_graphql::{ use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription}; use axum::{ extract::{FromRef, State}, + http::Method, response::{Html, IntoResponse}, routing::get, Router, @@ -22,8 +24,8 @@ use crate::{ config::Config, graphql::{subscription::StatusUpdate, MutationRoot, QueryRoot, SubscriptionRoot}, services::{ - invite_code_service::InviteCodeService, system_config_service::SystemConfigService, - user_service::UserService, + invite_code_service::InviteCodeService, mosaic_service::MosaicService, + system_config_service::SystemConfigService, user_service::UserService, }, }; @@ -47,12 +49,14 @@ pub fn create_router( let user_service = UserService::new(pool.clone(), config.jwt_secret.clone()); let invite_code_service = InviteCodeService::new(pool.clone()); let system_config_service = SystemConfigService::new(pool.clone()); + let mosaic_service = MosaicService::new(pool.clone()); let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot) .data(pool) .data(user_service) .data(invite_code_service) .data(system_config_service) + .data(mosaic_service) .data(config.clone()) .data(status_sender.clone()) .finish(); @@ -77,6 +81,20 @@ pub fn create_router( Router::new() .route("/", get(graphql_playground)) .route("/graphql", get(graphql_playground).post(graphql_handler)) + // .route_layer( + // RateLimitLayer::::builder() + // .with_route( + // (Method::GET, "/graphql"), + // Quota::new(Duration::from_millis(100), NonZero::new(10).unwrap()), + // ) + // .with_route( + // (Method::POST, "/graphql"), + // Quota::new(Duration::from_millis(100), NonZero::new(10).unwrap()), + // ) + // .with_global_fallback(false) + // .with_gc_interval(1000) + // .default_handle_error(), + // ) .route_service("/ws", GraphQLSubscription::new(schema)) .layer(CorsLayer::permissive()) .merge(router) diff --git a/src/graphql/subscription.rs b/src/graphql/subscription.rs index fb51798..0265781 100644 --- a/src/graphql/subscription.rs +++ b/src/graphql/subscription.rs @@ -7,6 +7,8 @@ use tokio::time::{interval, timeout}; use tracing::{error, info, warn}; use uuid::Uuid; +use crate::services::mosaic_service::{self, MosaicService}; + #[derive(SimpleObject, Clone, Debug)] pub struct StatusUpdate { pub id: Uuid, @@ -44,6 +46,9 @@ impl SubscriptionRoot { info!("新的WebSocket客户端连接到状态更新订阅"); + let mosaic_service = ctx.data::()?; + let last_data = mosaic_service.last_data("cn").await?; + return Ok(async_stream::stream! { // 发送初始连接确认消息 let welcome = StatusUpdate { @@ -52,7 +57,7 @@ impl SubscriptionRoot { message: "WebSocket连接已建立".to_string(), timestamp: Utc::now(), data: Some("websocket_connected".to_string()), - newest_dt: None, + newest_dt: last_data.map(|data| data.data_time.format("%Y%m%d%H%M%S").to_string()), }; yield welcome; diff --git a/src/listener/mod.rs b/src/listener/mod.rs index fcfb8b4..d060fc6 100644 --- a/src/listener/mod.rs +++ b/src/listener/mod.rs @@ -174,7 +174,7 @@ impl KafkaListener { ), timestamp: Utc::now(), data: Some(message.get_file_path().to_string()), - newest_dt: Some(message.get_data_time().to_string()), + newest_dt: Some(format!("{}00", message.get_data_time())), }; if let Err(e) = self.status_sender.send(status_update) { diff --git a/src/services/mod.rs b/src/services/mod.rs index 8457045..31c88d0 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,3 +1,4 @@ pub mod invite_code_service; +pub mod mosaic_service; pub mod system_config_service; pub mod user_service; diff --git a/src/services/mosaic_service.rs b/src/services/mosaic_service.rs new file mode 100644 index 0000000..3d1981f --- /dev/null +++ b/src/services/mosaic_service.rs @@ -0,0 +1,65 @@ +use argon2::password_hash::{rand_core::OsRng, SaltString}; +use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; +use async_graphql::{Error, Result}; +use chrono::{DateTime, Utc}; +use sea_query::{Expr, PostgresQueryBuilder}; +use sea_query_binder::SqlxBinder; +use sqlx::PgPool; +use tracing::info; +use uuid::Uuid; + +use crate::auth::Claims; +use crate::graphql::types::{ + CreateUserInput, LoginInput, LoginResponse, RegisterInput, UserInfoRespnose, +}; +use crate::models::user::{Role, User}; + +use crate::services::invite_code_service::InviteCodeService; +use crate::services::system_config_service::SystemConfigService; + +#[derive(Debug, Clone)] +pub struct DataIngestion { + pub id: Uuid, + pub ingestion_time: DateTime, + pub data_time: DateTime, + pub source: String, + pub storage_url: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +pub struct MosaicService { + pool: PgPool, +} + +impl MosaicService { + pub fn new(pool: PgPool) -> Self { + Self { pool } + } + + pub async fn last_data(&self, area: &str) -> Result> { + let result = sqlx::query_as!( + DataIngestion, + r#" + SELECT + id, + ingestion_time as "ingestion_time: chrono::DateTime", + data_time as "data_time: chrono::DateTime", + source, + storage_url, + created_at as "created_at: chrono::DateTime", + updated_at as "updated_at: chrono::DateTime" + FROM data_ingestion + WHERE source = $1 + ORDER BY data_time DESC + LIMIT 1 + "#, + area + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| Error::new(format!("Failed to get last data: {}", e)))?; + + Ok(result) + } +}