diff --git a/.github/workflows/cloudDeployment.yml b/.github/workflows/cloudDeployment.yml new file mode 100644 index 0000000..2c1d9a2 --- /dev/null +++ b/.github/workflows/cloudDeployment.yml @@ -0,0 +1,57 @@ +name: Deploy to Cloud + +on: + push: + branches: + - swiftparcel_web # Set this to the branch you want to deploy from + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up SSH + run: | + mkdir -p ~/.ssh/ + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ secrets.DROPLET_IP }} >> ~/.ssh/known_hosts + cat ~/.ssh/known_hosts # Add this line to confirm that the key is added + + - name: Check the structure + run: | + pwd + ls -la + + - name: Docker Login + run: | + echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin + + - name: Run dockerize_all.sh + run: | + chmod +x ./SwiftParcel/d-docker-compose/dockerize_all.sh + ./SwiftParcel/d-docker-compose/dockerize_all.sh + + - name: Check the structure 2 + run: | + pwd + ls -la + + - name: Test SSH Connection + run: | + ssh -vvv root@${{ secrets.DROPLET_IP }} "echo SSH Connection Successful" + + - name: Stop and Remove Containers + run: | + ssh root@${{ secrets.DROPLET_IP }} "docker ps -q --filter network=swiftparcel-network | xargs -r docker stop && docker ps -a -q --filter network=swiftparcel-network | xargs -r docker rm" + + - name: Clean up existing Cloud networks + run: | + ssh root@${{ secrets.DROPLET_IP }} "docker network rm swiftparcel-network || true" + + - name: Deploy to Cloud + run: | + scp -r ./SwiftParcel/d-docker-compose root@${{ secrets.DROPLET_IP }}:${{ secrets.CLOUD_PROJECT_PATH }} + ssh root@${{ secrets.DROPLET_IP }} "cd ${{ secrets.CLOUD_PROJECT_PATH }}/SwiftParcel/d-docker-compose && docker-compose -f ${{ secrets.CLOUD_DEPLOYMENT_FILE }} pull && docker-compose -f ${{ secrets.CLOUD_DEPLOYMENT_FILE }} up -d --force-recreate" \ No newline at end of file diff --git a/SwiftParcel.API.Gateway/Dockerfile b/SwiftParcel.API.Gateway/Dockerfile index 2fbf43d..e3ec6a2 100644 --- a/SwiftParcel.API.Gateway/Dockerfile +++ b/SwiftParcel.API.Gateway/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker -ENTRYPOINT ["dotnet", "SwiftParcel.Services.Customers.Api.dll"] +ENV ASPNETCORE_ENVIRONMENT production +ENTRYPOINT ["dotnet", "SwiftParcel.API.Gateway.dll"] diff --git a/SwiftParcel.API.Gateway/scripts/dockerize.sh b/SwiftParcel.API.Gateway/scripts/dockerize.sh new file mode 100755 index 0000000..fecdecb --- /dev/null +++ b/SwiftParcel.API.Gateway/scripts/dockerize.sh @@ -0,0 +1,12 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-apigateway-service:latest . + +docker tag swift-parcel-apigateway-service:latest adrianvsaint/swift-parcel-apigateway-service:latest + +docker push adrianvsaint/swift-parcel-apigateway-service + +docker push swiftparcel/swift-parcel-apigateway-service \ No newline at end of file diff --git a/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.json b/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.json index 45342e4..58c0b40 100644 --- a/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.json +++ b/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.json @@ -11,7 +11,7 @@ }, "seq": { "enabled": true, - "url": "http://localhost:5341", + "url": "http://seq:5341", "apiKey": "secret" } }, @@ -19,7 +19,7 @@ "enabled": true, "influxEnabled": false, "prometheusEnabled": true, - "influxUrl": "http://localhost:8086", + "influxUrl": "http://influx:8086", "database": "swiftparcel", "env": "local", "interval": 5 diff --git a/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.production.json b/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.production.json new file mode 100644 index 0000000..58c0b40 --- /dev/null +++ b/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/appsettings.production.json @@ -0,0 +1,27 @@ +{ + "logger": { + "excludePaths": ["/", "/ping", "/metrics"], + "console": { + "enabled": true + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + } + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + } +} \ No newline at end of file diff --git a/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/ntrada.yml b/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/ntrada.yml index bad04aa..73a696a 100644 --- a/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/ntrada.yml +++ b/SwiftParcel.API.Gateway/src/SwiftParcel.API.Gateway/ntrada.yml @@ -42,8 +42,8 @@ extensions: - Trace-ID - Total-Count - jwt: - issuerSigningKey: eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImV4cCI6MTY5ODYwMTg3NSwiaWF0IjoxNjk4NjAxODc1fQ.nxJlaEy9xNO4noVh84qD9BSoLvjq1xu4LGhwBaEF9Ec + jwt: + issuerSigningKey: eiquief5phee9pazo0Faegaez9gohThailiur5woy2befiech1oarai4aiLi6ahVecah3ie9Aiz6Peij validIssuer: swiftparcel validateAudience: false validateIssuer: true @@ -75,30 +75,30 @@ modules: identity: - path: identity + path: /identity routes: - upstream: /users/{userId} method: GET use: downstream downstream: identity-service/users/{userId} auth: true - claims: - role: admin + # claims: + # role: admin - upstream: /me method: GET use: downstream downstream: identity-service/me auth: true - + - upstream: /sign-up method: POST use: downstream downstream: identity-service/sign-up auth: false - resourceId: - property: userId - generate: true + # resourceId: + # property: userId + # generate: true - upstream: /sign-in method: POST @@ -107,48 +107,52 @@ modules: auth: false responseHeaders: content-type: application/json - + services: identity-service: localUrl: localhost:5004 url: identity-service orders: - path: orders + path: /orders routes: - - upstream: / + - upstream: /customerId={customerId} method: GET use: downstream - downstream: orders-service/orders?customerId=@user_id + downstream: orders-service/orders?customerId={customerId} auth: true + bind: + - customerId:{customerId} - upstream: /requests method: GET use: downstream - downstream: orders-service/orders/requests?customerId=@user_id + downstream: orders-service/orders/requests auth: true - - upstream: /office-worker/pending + - upstream: /office-worker method: GET use: downstream - downstream: orders-service/orders/office-worker/pending + downstream: orders-service/orders/office-worker auth: true - claims: - role: officeworker + # claims: + # role: officeworker - - upstream: /office-worker + - upstream: /office-worker/pending method: GET use: downstream - downstream: orders-service/orders/office-worker + downstream: orders-service/orders/office-worker/pending auth: true - claims: - role: officeworker + # claims: + # role: officeworker - upstream: /{orderId} method: GET use: downstream downstream: orders-service/orders/{orderId} auth: true + bind: + - orderId:{orderId} - upstream: /{orderId}/status method: GET @@ -172,20 +176,35 @@ modules: downstream: orders-service/orders/{orderId}/customer auth: true bind: - - customerId:@user_id - orderId:{orderId} - - upstream: /{orderId}/approve + - upstream: /{orderId}/office-worker/approve method: PUT use: downstream - downstream: orders-service/orders/{orderId}/approve + downstream: orders-service/orders/{orderId}/office-worker/approve auth: true bind: - orderId:{orderId} - - upstream: /{orderId}/cancel + - upstream: /{orderId}/office-worker/cancel method: PUT use: downstream + downstream: orders-service/orders/{orderId}/office-worker/cancel + auth: true + bind: + - orderId:{orderId} + + - upstream: /{orderId}/confirm + method: POST + use: downstream + downstream: orders-service/orders/{orderId}/confirm + auth: true + bind: + - orderId:{orderId} + + - upstream: /{orderId}/cancel + method: DELETE + use: downstream downstream: orders-service/orders/{orderId}/cancel auth: true bind: @@ -197,7 +216,7 @@ modules: url: orders-service customers: - path: customers + path: /customers routes: - upstream: / method: GET @@ -218,8 +237,8 @@ modules: use: downstream downstream: customers-service/customers/{customerId} auth: true - claims: - role: admin + # claims: + # role: admin - upstream: /{customerId}/state method: GET @@ -233,11 +252,11 @@ modules: method: POST use: downstream downstream: customers-service/customers - bind: - - customerId:@user_id - auth: true - payload: create_customer - schema: create_customer.schema + # bind: + # - customerId:@user_id + # auth: true + # payload: create_customer + # schema: create_customer.schema - upstream: /{customerId}/state/{state} method: PUT @@ -258,21 +277,23 @@ modules: parcels: - path: parcels + path: /parcels routes: - - upstream: / + - upstream: /customerId={customerId} method: GET use: downstream - downstream: parcels-service/parcels?customerId=@user_id + downstream: parcels-service/parcels?customerId={customerId} auth: true + bind: + - customerId:{customerId} - - upstream: / + - upstream: /office-worker method: GET use: downstream downstream: parcels-service/parcels/office-worker auth: true - claims: - role: officeworker + # claims: + # role: officeworker - upstream: /{parcelId} method: GET @@ -284,7 +305,7 @@ modules: method: GET use: downstream downstream: parcels-service/parcels/{parcelId}/offers - auth: true + auth: false - upstream: / method: POST @@ -303,7 +324,7 @@ modules: url: parcels-service pricing: - path: pricing + path: /pricing routes: - upstream: / method: GET @@ -317,41 +338,40 @@ modules: url: pricing-service deliveries: - path: deliveries + path: /deliveries routes: - upstream: /{deliveryId} method: GET use: downstream downstream: deliveries-service/deliveries/{deliveryId} auth: true - claims: - role: courier + # claims: + # role: courier - - upstream: / + - upstream: /courierId={courierId} method: GET use: downstream - downstream: deliveries-service/deliveries?courierId=@user_id + downstream: deliveries-service/deliveries?courierId={courierId} auth: true - claims: - role: courier + bind: + - courierId:{courierId} + # claims: + # role: courier - upstream: /pending method: GET use: downstream downstream: deliveries-service/deliveries/pending auth: true - claims: - role: courier + # claims: + # role: courier - - upstream: / + - upstream: /{deliveryId}/courier method: POST use: downstream downstream: deliveries-service/deliveries/{deliveryId}/courier auth: true - claims: - role: courier bind: - - courierId:@user_id - deliveryId:{deliveryId} - upstream: /{deliveryId}/fail @@ -359,30 +379,30 @@ modules: use: downstream downstream: deliveries-service/deliveries/{deliveryId}/fail auth: true - claims: - role: courier bind: - deliveryId:{deliveryId} + # claims: + # role: courier - upstream: /{deliveryId}/complete method: POST use: downstream downstream: deliveries-service/deliveries/{deliveryId}/complete auth: true - claims: - role: courier bind: - deliveryId:{deliveryId} + # claims: + # role: courier - upstream: /{deliveryId}/pick-up method: POST use: downstream downstream: deliveries-service/deliveries/{deliveryId}/pick-up auth: true - claims: - role: courier bind: - deliveryId:{deliveryId} + # claims: + # role: courier services: diff --git a/SwiftParcel.ExternalAPI.Lecturer/Dockerfile b/SwiftParcel.ExternalAPI.Lecturer/Dockerfile new file mode 100644 index 0000000..63f897c --- /dev/null +++ b/SwiftParcel.ExternalAPI.Lecturer/Dockerfile @@ -0,0 +1,13 @@ +# Use the .NET 6.0 SDK image to build the application +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /app +COPY . . +RUN dotnet publish src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api -c release -o out + +# Use the .NET 6.0 runtime image to run the application +FROM mcr.microsoft.com/dotnet/aspnet:6.0 +WORKDIR /app +COPY --from=build /app/out . +ENV ASPNETCORE_URLS http://*:80 +ENV ASPNETCORE_ENVIRONMENT production +ENTRYPOINT ["dotnet", "SwiftParcel.ExternalAPI.Lecturer.Api.dll"] diff --git a/SwiftParcel.ExternalAPI.Lecturer/scripts/build.sh b/SwiftParcel.ExternalAPI.Lecturer/scripts/build.sh old mode 100644 new mode 100755 diff --git a/SwiftParcel.ExternalAPI.Lecturer/scripts/dockerize.sh b/SwiftParcel.ExternalAPI.Lecturer/scripts/dockerize.sh new file mode 100755 index 0000000..db778fd --- /dev/null +++ b/SwiftParcel.ExternalAPI.Lecturer/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-external-api-lecturer-service:latest . + +docker tag swift-parcel-external-api-lecturer-service:latest adrianvsaint/swift-parcel-external-api-lecturer-service:latest + +docker push adrianvsaint/swift-parcel-external-api-lecturer-service diff --git a/SwiftParcel.ExternalAPI.Lecturer/scripts/start.sh b/SwiftParcel.ExternalAPI.Lecturer/scripts/start.sh old mode 100644 new mode 100755 diff --git a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api.csproj b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api.csproj index 4c3868b..18c45cc 100644 --- a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api.csproj +++ b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api.csproj @@ -1,7 +1,7 @@ - net7.0 + net6.0 disable enable diff --git a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/appsettings.production.json b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/appsettings.production.json new file mode 100644 index 0000000..564da52 --- /dev/null +++ b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/appsettings.production.json @@ -0,0 +1,124 @@ +{ + "app": { + "name": "SwiftParcel Lecturer API Service", + "service": "lecturer-api-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "lecturer-api-service", + "address": "lecturer-api-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": false, + "url": "http://fabio:9999", + "service": "lecturer-api-service" + }, + "httpClient": { + "type": "direct", + "retries": 3, + "services": { + "lecturer-api": "https://mini.currier.api.snet.com.pl", + "lecturer-api-identity": "https://indentitymanager.snet.com.pl/connect/token" + }, + "token": "dGVhbTJkOkVBQUE1MEI4LTkwQ0ItNDM2RS05ODY0LTRCQzc1QjU2RjNCRQ==", + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "AllowedHosts": "*", + "vault": { + "enabled": false, + "url": "http://127.0.0.1:8200", + "authType": "userpass", + "token": "secret", + "username": "user", + "password": "piotr-amadeusz-andrii-2023", + "dbusername": "andrii-courier-db-user", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "secret", + "path": "lecturer-api-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "lecturer-api-service", + "commonName": "lecturer-api-service.swiftparcel.com" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "lecturer-api-service", + "enabled": false, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority" + } + } + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://elk:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "lecturer-api-service", + "seed": false + }, + "rabbitMq": { + "hostnames": [ + "rabbitmq" + ] + } +} diff --git a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/development.appsettings.json b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/development.appsettings.json new file mode 100644 index 0000000..8a42b38 --- /dev/null +++ b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Api/SwiftParcel.ExternalAPI.Lecturer.Api/development.appsettings.json @@ -0,0 +1,162 @@ +{ + "app": { + "name": "SwiftParcel Lecturer API Service", + "service": "lecturer-api-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://localhost:8500", + "service": "lecturer-api-service", + "address": "docker.for.win.localhost", + "port": "5013", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": false, + "url": "http://localhost:9999", + "service": "lecturer-api-service" + }, + "httpClient": { + "type": "", + "retries": 3, + "services": { + "lecturer-api": "https://mini.currier.api.snet.com.pl", + "lecturer-api-identity": "https://indentitymanager.snet.com.pl/connect/token" + }, + "token": "dGVhbTJkOkVBQUE1MEI4LTkwQ0ItNDM2RS05ODY0LTRCQzc1QjU2RjNCRQ==", + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "AllowedHosts": "*", + "vault": { + "enabled": false, + "url": "http://127.0.0.1:8200", + "authType": "userpass", + "token": "secret", + "username": "user", + "password": "piotr-amadeusz-andrii-2023", + "dbusername": "andrii-courier-db-user", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "secret", + "path": "lecturer-api-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "lecturer-api-service", + "commonName": "lecturer-api-service.swiftparcel.com" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "lecturer-api-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority" + } + } + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "lecturer-api-service", + "seed": false + }, + "rabbitMq": { + "connectionName": "lecturer-api-service", + "retries": 3, + "retryInterval": 2, + "conventionsCasing": "snakeCase", + "logger": { + "enabled": true + }, + "username": "guest", + "password": "guest", + "virtualHost": "/", + "port": 5672, + "hostnames": [ + "localhost" + ], + "requestedConnectionTimeout": "00:00:30", + "requestedHeartbeat": "00:01:00", + "socketReadTimeout": "00:00:30", + "socketWriteTimeout": "00:00:30", + "continuationTimeout": "00:00:20", + "handshakeContinuationTimeout": "00:00:10", + "networkRecoveryInterval": "00:00:05", + "exchange": { + "declare": true, + "durable": true, + "autoDelete": false, + "type": "topic", + "name": "identity" + }, + "queue": { + "declare": true, + "durable": true, + "exclusive": false, + "autoDelete": false, + "template": "lecturer-api-service/{{exchange}}.{{message}}" + }, + "context": { + "enabled": true, + "header": "message_context" + }, + "spanContextHeader": "span_context" + } + } + \ No newline at end of file diff --git a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application.csproj b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application.csproj index 0090c91..46f93c3 100644 --- a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application.csproj +++ b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application/SwiftParcel.ExternalAPI.Lecturer.Application.csproj @@ -1,7 +1,7 @@ - net7.0 + net6.0 enable disable diff --git a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core.csproj b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core.csproj index 0a76499..141e38f 100644 --- a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core.csproj +++ b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core/SwiftParcel.ExternalAPI.Lecturer.Core.csproj @@ -1,7 +1,7 @@ - net7.0 + net6.0 enable disable diff --git a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure.csproj b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure.csproj index d3e9ba5..8a13e62 100644 --- a/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure.csproj +++ b/SwiftParcel.ExternalAPI.Lecturer/src/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure/SwiftParcel.ExternalAPI.Lecturer.Infrastructure.csproj @@ -1,7 +1,7 @@ - net7.0 + net6.0 enable disable diff --git a/SwiftParcel.Services.Availability/src/SwiftParcel.Services.Availability.Api/SwiftParcel.Services.Availability.Api/appsettings.json b/SwiftParcel.Services.Availability/src/SwiftParcel.Services.Availability.Api/SwiftParcel.Services.Availability.Api/appsettings.json index 53e051f..4f15a90 100644 --- a/SwiftParcel.Services.Availability/src/SwiftParcel.Services.Availability.Api/SwiftParcel.Services.Availability.Api/appsettings.json +++ b/SwiftParcel.Services.Availability/src/SwiftParcel.Services.Availability.Api/SwiftParcel.Services.Availability.Api/appsettings.json @@ -121,7 +121,7 @@ "virtualHost": "/", "port": 5672, "hostnames": [ - "localhost" + "rabbitmq" ], "requestedConnectionTimeout": "00:00:30", "requestedHeartbeat": "00:01:00", diff --git a/SwiftParcel.Services.Couriers/src/SwiftParcel.Services.Couriers.Api/SwiftParcel.Services.Couriers.Api/appsettings.json b/SwiftParcel.Services.Couriers/src/SwiftParcel.Services.Couriers.Api/SwiftParcel.Services.Couriers.Api/appsettings.json index e0a0e06..641cdd3 100644 --- a/SwiftParcel.Services.Couriers/src/SwiftParcel.Services.Couriers.Api/SwiftParcel.Services.Couriers.Api/appsettings.json +++ b/SwiftParcel.Services.Couriers/src/SwiftParcel.Services.Couriers.Api/SwiftParcel.Services.Couriers.Api/appsettings.json @@ -119,7 +119,7 @@ "virtualHost": "/", "port": 5672, "hostnames": [ - "localhost" + "rabbitmq" ], "requestedConnectionTimeout": "00:00:30", "requestedHeartbeat": "00:01:00", diff --git a/SwiftParcel.Services.Customers/Dockerfile b/SwiftParcel.Services.Customers/Dockerfile index 940cb97..a689389 100644 --- a/SwiftParcel.Services.Customers/Dockerfile +++ b/SwiftParcel.Services.Customers/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.Customers.Api.dll"] diff --git a/SwiftParcel.Services.Customers/scripts/dockerize.sh b/SwiftParcel.Services.Customers/scripts/dockerize.sh new file mode 100755 index 0000000..78bfbf2 --- /dev/null +++ b/SwiftParcel.Services.Customers/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-customers-service:latest . + +docker tag swift-parcel-customers-service:latest adrianvsaint/swift-parcel-customers-service:latest + +docker push adrianvsaint/swift-parcel-customers-service diff --git a/SwiftParcel.Services.Customers/src/SwiftParcel.Services.Customers.Api/SwiftParcel.Services.Customers.Api/appsettings.production.json b/SwiftParcel.Services.Customers/src/SwiftParcel.Services.Customers.Api/SwiftParcel.Services.Customers.Api/appsettings.production.json new file mode 100644 index 0000000..fde6886 --- /dev/null +++ b/SwiftParcel.Services.Customers/src/SwiftParcel.Services.Customers.Api/SwiftParcel.Services.Customers.Api/appsettings.production.json @@ -0,0 +1,176 @@ +{ + "app": { + "name": "SwiftParcel Customers Service", + "service": "customers-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "customers-service", + "address": "customers-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://fabio:9999", + "service": "customers-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": {}, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://elkm:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "customers", + "udpHost": "jaeger", + "udpPort": 6831, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "customers-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "hostnames": [ + "rabbitmq" + ] + }, + "redis": { + "connectionString": "redis", + "instance": "customers:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "security": { + "certificate": { + "enabled": false, + "header": "Certificate", + "skipRevocationCheck": false, + "allowedDomains": ["swiftparcel.io"], + "allowSubdomains": true, + "allowedHosts": [ + "localhost" + ], + "acl": { + "availability-service": { + "validIssuer": "localhost", + "permissions": [ + "customers:read" + ] + } + } + } + }, + "vault": { + "enabled": false, + "url": "http://localhost:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "kv", + "path": "customers-service/settings" + }, + "pki": { + "enabled": true, + "roleName": "customers-service", + "commonName": "customers-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "customers-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } +} \ No newline at end of file diff --git a/SwiftParcel.Services.Deliveries/Dockerfile b/SwiftParcel.Services.Deliveries/Dockerfile index 44ed60e..ba9d1fc 100644 --- a/SwiftParcel.Services.Deliveries/Dockerfile +++ b/SwiftParcel.Services.Deliveries/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.Deliveries.Api.dll"] diff --git a/SwiftParcel.Services.Deliveries/scripts/dockerize.sh b/SwiftParcel.Services.Deliveries/scripts/dockerize.sh new file mode 100755 index 0000000..311f841 --- /dev/null +++ b/SwiftParcel.Services.Deliveries/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-deliveries-service:latest . + +docker tag swift-parcel-deliveries-service:latest adrianvsaint/swift-parcel-deliveries-service:latest + +docker push adrianvsaint/swift-parcel-deliveries-service diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.json b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.json index 43cd284..6f1d68c 100644 --- a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.json +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.json @@ -119,7 +119,7 @@ "virtualHost": "/", "port": 5672, "hostnames": [ - "localhost" + "rabbitmq" ], "requestedConnectionTimeout": "00:00:30", "requestedHeartbeat": "00:01:00", diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.production.json b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.production.json new file mode 100644 index 0000000..efafaeb --- /dev/null +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/appsettings.production.json @@ -0,0 +1,157 @@ +{ + "app": { + "name": "SwiftParcel Deliveries Service", + "service": "deliveries-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "deliveries-service", + "address": "deliveries-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://fabio:9999", + "service": "deliveries-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": {}, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://elk:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "deliveries", + "udpHost": "jaeger", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "deliveries-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "hostnames": [ + "rabbitmq" + ] + }, + "redis": { + "connectionString": "redis", + "instance": "deliveries:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "vault": { + "enabled": false, + "url": "http://vault:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": false, + "engineVersion": 2, + "mountPoint": "kv", + "path": "deliveries-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "deliveries-service", + "commonName": "deliveries-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "deliveries-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } +} \ No newline at end of file diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/development.appsettings.json b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/development.appsettings.json new file mode 100644 index 0000000..5b77045 --- /dev/null +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Api/SwiftParcel.Services.Deliveries.Api/development.appsettings.json @@ -0,0 +1,194 @@ +{ + "app": { + "name": "SwiftParcel Deliveries Service", + "service": "deliveries-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://localhost:8500", + "service": "deliveries-service", + "address": "docker.for.win.localhost", + "port": "5004", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://localhost:9999", + "service": "deliveries-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": {}, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "deliveries", + "udpHost": "localhost", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://localhost:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "deliveries-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "connectionName": "deliveries-service", + "retries": 3, + "retryInterval": 2, + "conventionsCasing": "snakeCase", + "logger": { + "enabled": true + }, + "username": "guest", + "password": "guest", + "virtualHost": "/", + "port": 5672, + "hostnames": [ + "rabbitmq" + ], + "requestedConnectionTimeout": "00:00:30", + "requestedHeartbeat": "00:01:00", + "socketReadTimeout": "00:00:30", + "socketWriteTimeout": "00:00:30", + "continuationTimeout": "00:00:20", + "handshakeContinuationTimeout": "00:00:10", + "networkRecoveryInterval": "00:00:05", + "exchange": { + "declare": true, + "durable": true, + "autoDelete": false, + "type": "topic", + "name": "deliveries" + }, + "queue": { + "declare": true, + "durable": true, + "exclusive": false, + "autoDelete": false, + "template": "deliveries-service/{{exchange}}.{{message}}" + }, + "context": { + "enabled": true, + "header": "message_context" + }, + "spanContextHeader": "span_context" + }, + "redis": { + "connectionString": "localhost", + "instance": "deliveries:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "vault": { + "enabled": false, + "url": "http://localhost:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "kv", + "path": "deliveries-service/settings" + }, + "pki": { + "enabled": true, + "roleName": "deliveries-service", + "commonName": "deliveries-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "deliveries-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } + } \ No newline at end of file diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/CompleteDeliveryHandler.cs b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/CompleteDeliveryHandler.cs index 5fac85a..ecbfdb1 100644 --- a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/CompleteDeliveryHandler.cs +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/CompleteDeliveryHandler.cs @@ -11,14 +11,16 @@ internal sealed class CompleteDeliveryHandler : ICommandHandler private readonly IMessageBroker _messageBroker; private readonly IEventMapper _eventMapper; private readonly IDateTimeProvider _dateTimeProvider; + private readonly IAppContext _appContext; public FailDeliveryHandler(IDeliveriesRepository repository, IMessageBroker messageBroker, - IEventMapper eventMapper, IDateTimeProvider dateTimeProvider) + IEventMapper eventMapper, IDateTimeProvider dateTimeProvider, IAppContext appContext) { _repository = repository; _messageBroker = messageBroker; _eventMapper = eventMapper; _dateTimeProvider = dateTimeProvider; + _appContext = appContext; } public async Task HandleAsync(FailDelivery command) @@ -28,6 +30,11 @@ public async Task HandleAsync(FailDelivery command) { throw new DeliveryNotFoundException(command.DeliveryId); } + var identity = _appContext.Identity; + if (delivery.CourierId != identity.Id) + { + throw new UnauthorizedDeliveryAccessException(command.DeliveryId, identity.Id); + } delivery.Fail(_dateTimeProvider.Now, command.DeliveryAttemptDate, command.Reason); await _repository.UpdateAsync(delivery); diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/PickUpDeliveryHandler.cs b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/PickUpDeliveryHandler.cs index 9bf80fd..b895a019 100644 --- a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/PickUpDeliveryHandler.cs +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Commands/Handlers/PickUpDeliveryHandler.cs @@ -12,13 +12,15 @@ public class PickUpDeliveryHandler : ICommandHandler private readonly IMessageBroker _messageBroker; private readonly IEventMapper _eventMapper; private readonly IDateTimeProvider _dateTimeProvider; + private readonly IAppContext _appContext; public PickUpDeliveryHandler(IDeliveriesRepository repository, IMessageBroker messageBroker, - IEventMapper eventMapper, IDateTimeProvider dateTimeProvider) + IEventMapper eventMapper, IDateTimeProvider dateTimeProvider, IAppContext appContext) { _repository = repository; _messageBroker = messageBroker; _eventMapper = eventMapper; _dateTimeProvider = dateTimeProvider; + _appContext = appContext; } public async Task HandleAsync(PickUpDelivery command) { @@ -27,6 +29,11 @@ public async Task HandleAsync(PickUpDelivery command) { throw new DeliveryNotFoundException(command.DeliveryId); } + var identity = _appContext.Identity; + if (delivery.CourierId != identity.Id) + { + throw new UnauthorizedDeliveryAccessException(command.DeliveryId, identity.Id); + } delivery.PickUp(_dateTimeProvider.Now); await _repository.UpdateAsync(delivery); diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Exceptions/UnauthorizedDeliveryAccessException.cs b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Exceptions/UnauthorizedDeliveryAccessException.cs new file mode 100644 index 0000000..17bc34b --- /dev/null +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Application/SwiftParcel.Services.Deliveries.Application/Exceptions/UnauthorizedDeliveryAccessException.cs @@ -0,0 +1,16 @@ +namespace SwiftParcel.Services.Deliveries.Application.Exceptions +{ + public class UnauthorizedDeliveryAccessException : AppException + { + public override string Code { get; } = "unauthorized_delivery_access"; + public Guid DeliveryId { get; } + public Guid CourierId { get; } + + public UnauthorizedDeliveryAccessException(Guid deliveryId, Guid courierId) + : base($"Unauthorized access to delivery with id: '{deliveryId}' by customer with id: '{courierId}'.") + { + DeliveryId = deliveryId; + CourierId = courierId; + } + } +} \ No newline at end of file diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesHandler.cs b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesHandler.cs index 4cca6bd..7a5f1cd 100644 --- a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesHandler.cs +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesHandler.cs @@ -1,5 +1,6 @@ using Convey.CQRS.Queries; using Convey.Persistence.MongoDB; +using SwiftParcel.Services.Deliveries.Application; using SwiftParcel.Services.Deliveries.Application.DTO; using SwiftParcel.Services.Deliveries.Application.Queries; using SwiftParcel.Services.Deliveries.Infrastructure.Mongo.Documents; @@ -9,12 +10,23 @@ namespace SwiftParcel.Services.Deliveries.Infrastructure.Mongo.Queries.Handlers public class GetDeliveriesHandler: IQueryHandler> { private readonly IMongoRepository _repository; + private readonly IAppContext _appContext; - public GetDeliveriesHandler(IMongoRepository repository) - => _repository = repository; + public GetDeliveriesHandler(IMongoRepository repository, + IAppContext appContext) + { + _repository = repository; + _appContext = appContext; + } public async Task> HandleAsync(GetDeliveries query) { + var identity = _appContext.Identity; + if(identity.Id != query.CourierId) + { + return Enumerable.Empty(); + } + var documents = await _repository.FindAsync(d => d.CourierId == query.CourierId); return documents.Select(d => d.AsDto()); diff --git a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesPendingHandler.cs b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesPendingHandler.cs index 1be10d7..60b0995 100644 --- a/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesPendingHandler.cs +++ b/SwiftParcel.Services.Deliveries/src/SwiftParcel.Services.Deliveries.Infrastructure/SwiftParcel.Services.Deliveries.Infrastructure/Mongo/Queries/Handlers/GetDeliveriesPendingHandler.cs @@ -1,5 +1,6 @@ using Convey.CQRS.Queries; using Convey.Persistence.MongoDB; +using SwiftParcel.Services.Deliveries.Application; using SwiftParcel.Services.Deliveries.Application.DTO; using SwiftParcel.Services.Deliveries.Application.Queries; using SwiftParcel.Services.Deliveries.Core.Entities; @@ -11,12 +12,22 @@ namespace SwiftParcel.Services.Deliveries.Infrastructure.Mongo.Queries.Handlers public class GetDeliveriesPendingHandler: IQueryHandler> { private readonly IMongoRepository _repository; + private readonly IAppContext _appContext; - public GetDeliveriesPendingHandler(IMongoRepository repository) - => _repository = repository; - + public GetDeliveriesPendingHandler(IMongoRepository repository, + IAppContext appContext) + { + _repository = repository; + _appContext = appContext; + } public async Task> HandleAsync(GetDeliveriesPending query) { + var identity = _appContext.Identity; + if(!identity.IsCourier && !identity.IsOfficeWorker) + { + return Enumerable.Empty(); + } + var documents = await _repository.FindAsync(d => d.Status == DeliveryStatus.Unassigned); return documents.Select(d => d.AsDto()); diff --git a/SwiftParcel.Services.Orders/Dockerfile b/SwiftParcel.Services.Orders/Dockerfile index b66251e..021c213 100644 --- a/SwiftParcel.Services.Orders/Dockerfile +++ b/SwiftParcel.Services.Orders/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.Orders.Api.dll"] diff --git a/SwiftParcel.Services.Orders/scripts/dockerize.sh b/SwiftParcel.Services.Orders/scripts/dockerize.sh new file mode 100755 index 0000000..67de258 --- /dev/null +++ b/SwiftParcel.Services.Orders/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-orders-service:latest . + +docker tag swift-parcel-orders-service:latest adrianvsaint/swift-parcel-orders-service:latest + +docker push adrianvsaint/swift-parcel-orders-service diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/Program.cs b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/Program.cs index dabcc42..304f195 100644 --- a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/Program.cs +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/Program.cs @@ -40,7 +40,7 @@ public static async Task Main(string[] args) .Get("orders/{orderId}/status") .Delete("orders/{orderId}") .Post("orders", - afterDispatch: (cmd, ctx) => ctx.Response.Created($"orders/{cmd.OrderId}/status")) + afterDispatch: (cmd, ctx) => ctx.Response.Created($"orders/{cmd.OrderId}/status", cmd.OrderId)) .Post("orders/{orderId}/customer", afterDispatch: (cmd, ctx) => ctx.Response.Created($"orders/{cmd.OrderId}")) .Put("orders/{orderId}/office-worker/approve") diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/appsettings.production.json b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/appsettings.production.json new file mode 100644 index 0000000..b249d99 --- /dev/null +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/appsettings.production.json @@ -0,0 +1,165 @@ +{ + "app": { + "name": "SwiftParcel Orders Service", + "service": "orders-service", + "version": "1" + }, + "brevoApiKey": { + "ApiKey": "", + "SenderEmail": "swiftparcel2023@gmail.com", + "SenderName": "SwiftParcel" + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "orders-service", + "address": "orders-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": false, + "url": "http://fabio:9999", + "service": "orders-service" + }, + "httpClient": { + "type": "direct", + "retries": 3, + "services": { + "parcels": "parcels-service", + "lecturer-api": "lecturer-api" + }, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://elk:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "orders", + "udpHost": "jaeger", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "swiftparcel", + "env": "docker", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "orders-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "hostnames": [ + "rabbitmq" + ] + }, + "redis": { + "connectionString": "redis", + "instance": "orders:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "vault": { + "enabled": false, + "url": "http://vault:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "kv", + "path": "orders-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "orders-service", + "commonName": "orders-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "orders-service", + "enabled": false, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } +} \ No newline at end of file diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/development.appsettings.json b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/development.appsettings.json new file mode 100644 index 0000000..954bf2f --- /dev/null +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Api/SwiftParcel.Services.Orders.Api/development.appsettings.json @@ -0,0 +1,202 @@ +{ + "app": { + "name": "SwiftParcel Orders Service", + "service": "orders-service", + "version": "1" + }, + "brevoApiKey": { + "ApiKey": "", + "SenderEmail": "swiftparcel2023@gmail.com", + "SenderName": "SwiftParcel" + }, + "consul": { + "enabled": true, + "url": "http://localhost:8500", + "service": "orders-service", + "address": "docker.for.win.localhost", + "port": "5006", + "pingEnabled": false, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": false, + "url": "http://localhost:9999", + "service": "orders-service" + }, + "httpClient": { + "type": "direct", + "retries": 3, + "services": { + "parcels": "http://localhost:5007", + "lecturer-api": "http://localhost:5137" + }, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "orders", + "udpHost": "localhost", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://localhost:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "orders-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "connectionName": "orders-service", + "retries": 3, + "retryInterval": 2, + "conventionsCasing": "snakeCase", + "logger": { + "enabled": true + }, + "username": "guest", + "password": "guest", + "virtualHost": "/", + "port": 5672, + "hostnames": [ + "localhost" + ], + "requestedConnectionTimeout": "00:00:30", + "requestedHeartbeat": "00:01:00", + "socketReadTimeout": "00:00:30", + "socketWriteTimeout": "00:00:30", + "continuationTimeout": "00:00:20", + "handshakeContinuationTimeout": "00:00:10", + "networkRecoveryInterval": "00:00:05", + "exchange": { + "declare": true, + "durable": true, + "autoDelete": false, + "type": "topic", + "name": "orders" + }, + "queue": { + "declare": true, + "durable": true, + "exclusive": false, + "autoDelete": false, + "template": "orders-service/{{exchange}}.{{message}}" + }, + "context": { + "enabled": true, + "header": "message_context" + }, + "spanContextHeader": "span_context" + }, + "redis": { + "connectionString": "localhost", + "instance": "orders:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "vault": { + "enabled": false, + "url": "http://localhost:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "kv", + "path": "orders-service/settings" + }, + "pki": { + "enabled": true, + "roleName": "orders-service", + "commonName": "orders-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "orders-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } + } \ No newline at end of file diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Application/SwiftParcel.Services.Orders.Application/Commands/Handlers/CreateOrderMiniCurrierHandler.cs b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Application/SwiftParcel.Services.Orders.Application/Commands/Handlers/CreateOrderMiniCurrierHandler.cs index ca10294..cbf6273 100644 --- a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Application/SwiftParcel.Services.Orders.Application/Commands/Handlers/CreateOrderMiniCurrierHandler.cs +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Application/SwiftParcel.Services.Orders.Application/Commands/Handlers/CreateOrderMiniCurrierHandler.cs @@ -16,7 +16,8 @@ public CreateOrderMiniCurrierHandler(ILecturerApiServiceClient lecturerApiServic public async Task HandleAsync(CreateOrderMiniCurrier command, CancellationToken cancellationToken) { var orderValidation = new Order(Guid.Empty, Guid.Empty, - OrderStatus.WaitingForDecision, DateTime.Now, command.Name, command.Email, command.Address); + OrderStatus.WaitingForDecision, DateTime.Now, command.Name, command.Email, command.Address, + null, null, null, null, string.Empty, string.Empty, null); var response = await _lecturerApiServiceClient.PostOfferAsync(command); if (response == null) diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Core/SwiftParcel.Services.Orders.Core/Entities/Order.cs b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Core/SwiftParcel.Services.Orders.Core/Entities/Order.cs index 5d4fab6..93b223e 100644 --- a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Core/SwiftParcel.Services.Orders.Core/Entities/Order.cs +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Core/SwiftParcel.Services.Orders.Core/Entities/Order.cs @@ -27,7 +27,9 @@ public class Order : AggregateRoot public Order(AggregateId id, Guid? customerId, OrderStatus status, DateTime createdAt, - string buyerName, string buyerEmail, Address buyerAddress, Parcel parcel = null) + string buyerName, string buyerEmail, Address buyerAddress, DateTime? decisionDate, + DateTime? pickedUpAt, DateTime? deliveredAt, DateTime? cannotDeliverAt, string cancellationReason, + string cannotDeliverReason, Parcel parcel = null) { Id = id; CustomerId = customerId; @@ -46,19 +48,20 @@ public Order(AggregateId id, Guid? customerId, OrderStatus status, DateTime crea Parcel = parcel; - DecisionDate = null; - PickedUpAt = null; - DeliveredAt = null; - CannotDeliverAt = null; - CancellationReason = string.Empty; - CannotDeliverReason = string.Empty; + DecisionDate = decisionDate; + PickedUpAt = pickedUpAt; + DeliveredAt = deliveredAt; + CannotDeliverAt = cannotDeliverAt; + CancellationReason = cancellationReason; + CannotDeliverReason = cannotDeliverReason; } public static Order Create(AggregateId id, Guid customerId, OrderStatus status, DateTime createdAt, string buyerName, string buyerEmail, Address buyerAddress) { var order = new Order(id, customerId == Guid.Empty ? null : customerId, - status, createdAt, buyerName, buyerEmail, buyerAddress); + status, createdAt, buyerName, buyerEmail, buyerAddress, null, null, null, + null, string.Empty, string.Empty); order.AddEvent(new OrderStateChanged(order)); return order; diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/Documents/Extensions.cs b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/Documents/Extensions.cs index 849415f..2a703fe 100644 --- a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/Documents/Extensions.cs +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/Documents/Extensions.cs @@ -8,7 +8,9 @@ public static class Extensions { public static Order AsEntity(this OrderDocument document) => new Order(document.Id, document.CustomerId, document.Status, document.OrderRequestDate, - document.BuyerName, document.BuyerEmail, document.BuyerAddress, document.Parcel); + document.BuyerName, document.BuyerEmail, document.BuyerAddress, + document.DecisionDate, document.PickedUpAt, document.DeliveredAt, document.CannotDeliverAt, + document.CancellationReason, document.CannotDeliverReason, document.Parcel); public static OrderDocument AsDocument(this Order entity) => new OrderDocument diff --git a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/QueriesHandlers/GetOrdersHandler.cs b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/QueriesHandlers/GetOrdersHandler.cs index 86c3d3a..580a5bc 100644 --- a/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/QueriesHandlers/GetOrdersHandler.cs +++ b/SwiftParcel.Services.Orders/src/SwiftParcel.Services.Orders.Infrastructure/SwiftParcel.Services.Orders.Infrastructure/Mongo/QueriesHandlers/GetOrdersHandler.cs @@ -38,8 +38,7 @@ public async Task> HandleAsync(GetOrders query, Cancellati return Enumerable.Empty(); } - documents = documents.Where(p => p.CustomerId == query.CustomerId && - p.Status != OrderStatus.WaitingForDecision && p.Status != OrderStatus.Approved); + documents = documents.Where(p => p.CustomerId == query.CustomerId); var orders = await documents.ToListAsync(); var ordersDto = orders.Select(p => p.AsDto()); diff --git a/SwiftParcel.Services.OrdersCreator/Dockerfile b/SwiftParcel.Services.OrdersCreator/Dockerfile index b5f56ab..06d8abe 100644 --- a/SwiftParcel.Services.OrdersCreator/Dockerfile +++ b/SwiftParcel.Services.OrdersCreator/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.OrderMaker.Api.dll"] diff --git a/SwiftParcel.Services.OrdersCreator/src/SwiftParcel.Services.OrdersCreator/appsettings.json b/SwiftParcel.Services.OrdersCreator/src/SwiftParcel.Services.OrdersCreator/appsettings.json index 3724cf9..2948b36 100644 --- a/SwiftParcel.Services.OrdersCreator/src/SwiftParcel.Services.OrdersCreator/appsettings.json +++ b/SwiftParcel.Services.OrdersCreator/src/SwiftParcel.Services.OrdersCreator/appsettings.json @@ -99,7 +99,7 @@ "virtualHost": "/", "port": 5672, "hostnames": [ - "localhost" + "rabbitmq" ], "requestedConnectionTimeout": "00:00:30", "requestedHeartbeat": "00:01:00", diff --git a/SwiftParcel.Services.Parcels/Dockerfile b/SwiftParcel.Services.Parcels/Dockerfile index 7825062..5b50a72 100644 --- a/SwiftParcel.Services.Parcels/Dockerfile +++ b/SwiftParcel.Services.Parcels/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.Parcels.Api.dll"] diff --git a/SwiftParcel.Services.Parcels/SwiftParcel.Services.Parcels.sln b/SwiftParcel.Services.Parcels/SwiftParcel.Services.Parcels.sln index fc22361..02ce8b0 100644 --- a/SwiftParcel.Services.Parcels/SwiftParcel.Services.Parcels.sln +++ b/SwiftParcel.Services.Parcels/SwiftParcel.Services.Parcels.sln @@ -7,28 +7,30 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{676BEB4D-578 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SwiftParcel.Services.Parcels.Api", "SwiftParcel.Services.Parcels.Api", "{DA4A44B7-960C-4E6D-AC68-EF00868B61FB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftParcel.Services.Parcels.Api", "src\SwiftParcel.Services.Parcels.Api\SwiftParcel.Services.Parcels.Api\SwiftParcel.Services.Parcels.Api.csproj", "{142357CB-BD9F-40B8-9DDD-825D8F7A2359}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwiftParcel.Services.Parcels.Api", "src\SwiftParcel.Services.Parcels.Api\SwiftParcel.Services.Parcels.Api\SwiftParcel.Services.Parcels.Api.csproj", "{142357CB-BD9F-40B8-9DDD-825D8F7A2359}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SwiftParcel.Services.Parcels.Application", "SwiftParcel.Services.Parcels.Application", "{8377E9C0-B395-4885-BBCD-0825A86E576D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftParcel.Services.Parcels.Application", "src\SwiftParcel.Services.Parcels.Application\SwiftParcel.Services.Parcels.Application\SwiftParcel.Services.Parcels.Application.csproj", "{E3ED79DA-99CB-4720-B698-5D255840B9EF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwiftParcel.Services.Parcels.Application", "src\SwiftParcel.Services.Parcels.Application\SwiftParcel.Services.Parcels.Application\SwiftParcel.Services.Parcels.Application.csproj", "{E3ED79DA-99CB-4720-B698-5D255840B9EF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SwiftParcel.Services.Parcels.Core", "SwiftParcel.Services.Parcels.Core", "{355ED247-1070-4DE4-BB87-148538CA28AD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftParcel.Services.Parcels.Core", "src\SwiftParcel.Services.Parcels.Core\SwiftParcel.Services.Parcels.Core\SwiftParcel.Services.Parcels.Core.csproj", "{DF4B8924-6E29-468A-98B0-B3CC5B42FF92}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwiftParcel.Services.Parcels.Core", "src\SwiftParcel.Services.Parcels.Core\SwiftParcel.Services.Parcels.Core\SwiftParcel.Services.Parcels.Core.csproj", "{DF4B8924-6E29-468A-98B0-B3CC5B42FF92}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SwiftParcel.Services.Parcels.Infrastructure", "SwiftParcel.Services.Parcels.Infrastructure", "{BE086060-031B-48E4-984A-95518F25E153}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwiftParcel.Services.Parcels.Infrastructure", "src\SwiftParcel.Services.Parcels.Infrastructure\SwiftParcel.Services.Parcels.Infrastructure\SwiftParcel.Services.Parcels.Infrastructure.csproj", "{098CDE85-D73E-4FBC-8349-742411248CA9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SwiftParcel.Services.Parcels.Infrastructure", "src\SwiftParcel.Services.Parcels.Infrastructure\SwiftParcel.Services.Parcels.Infrastructure\SwiftParcel.Services.Parcels.Infrastructure.csproj", "{098CDE85-D73E-4FBC-8349-742411248CA9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5013E664-BF01-4FFB-9A4B-8840D57F61B5}" + ProjectSection(SolutionItems) = preProject + app.http = app.http + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {142357CB-BD9F-40B8-9DDD-825D8F7A2359}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {142357CB-BD9F-40B8-9DDD-825D8F7A2359}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -47,6 +49,9 @@ Global {098CDE85-D73E-4FBC-8349-742411248CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU {098CDE85-D73E-4FBC-8349-742411248CA9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection GlobalSection(NestedProjects) = preSolution {DA4A44B7-960C-4E6D-AC68-EF00868B61FB} = {676BEB4D-578A-41D1-A70C-1B59E357237E} {142357CB-BD9F-40B8-9DDD-825D8F7A2359} = {DA4A44B7-960C-4E6D-AC68-EF00868B61FB} diff --git a/SwiftParcel.Services.Parcels/app.http b/SwiftParcel.Services.Parcels/app.http new file mode 100644 index 0000000..8bce3c8 --- /dev/null +++ b/SwiftParcel.Services.Parcels/app.http @@ -0,0 +1,50 @@ +@ParcelsApi = http://localhost:5007 + +POST {{ParcelsApi}}/parcels +Content-Type: application/json + +{ + "ParcelId": "00000000-0000-0000-0000-000000000000", + "CustomerId": "86b80e0c-167e-452f-af8c-a7bf6267b208", + "Description": "Please don't throw", + "Width": 0.5, + "Height": 0.5, + "Depth": 0.5, + "Weight": 0.5, + "SourceStreet": "Plac Politechniki", + "SourceBuildingNumber": "21", + "SourceApartmentNumber": "37", + "SourceCity": "Warsaw", + "SourceZipCode": "00-303", + "SourceCountry": "Poland", + "DestinationStreet": "Koszykowa", + "DestinationBuildingNumber": "3", + "DestinationApartmentNumber": null, + "DestinationCity": "Warsaw", + "DestinationZipCode": "00-323", + "DestinationCountry": "Poland", + "Priority": "High", + "AtWeekend": true, + "PickupDate": "2024-01-28T12:35:56.450Z", + "DeliveryDate": "2024-01-30T12:35:56.450Z", + "IsCompany": true, + "VipPackage": true +} + +### + +GET {{ParcelsApi}}/parcels/8cb8912b-2a7d-4976-9dcd-beb1ecb702b4/offers + +### + +GET {{ParcelsApi}}/parcels/8cb8912b-2a7d-4976-9dcd-beb1ecb702b4 + +### + +GET {{ParcelsApi}}/parcels?customerId=86b80e0c-167e-452f-af8c-a7bf6267b208 + +### + +GET {{ParcelsApi}}/parcels/office-worker + +### \ No newline at end of file diff --git a/SwiftParcel.Services.Parcels/scripts/dockerize.sh b/SwiftParcel.Services.Parcels/scripts/dockerize.sh new file mode 100755 index 0000000..2346351 --- /dev/null +++ b/SwiftParcel.Services.Parcels/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-parcels-service:latest . + +docker tag swift-parcel-parcels-service:latest adrianvsaint/swift-parcel-parcels-service:latest + +docker push adrianvsaint/swift-parcel-parcels-service diff --git a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/Program.cs b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/Program.cs index 7925349..762983a 100644 --- a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/Program.cs +++ b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/Program.cs @@ -39,7 +39,7 @@ public static async Task Main(string[] args) .Get("parcels/{parcelId}") .Get>("parcels/{parcelId}/offers") .Post("parcels", - afterDispatch: (cmd, ctx) => ctx.Response.Created($"parcels/{cmd.ParcelId}/offers")) + afterDispatch: (cmd, ctx) => ctx.Response.Created($"parcels/{cmd.ParcelId}/offers", cmd.ParcelId)) .Delete("parcels/{parcelId}") )) .UseLogging() diff --git a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/appsettings.production.json b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/appsettings.production.json new file mode 100644 index 0000000..e996b83 --- /dev/null +++ b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/appsettings.production.json @@ -0,0 +1,163 @@ +{ + "app": { + "name": "SwiftParcel Parcels Service", + "service": "parcels-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "parcels-service", + "address": "parcels-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://fabio:9999", + "services": { + "pricing": "pricing-service", + "lecturer-api": "lecturer-api" + } + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": { + "pricing": "http://localhost:5008", + "lecturer-api": "http://localhost:5137" + }, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": [ "/", "/ping", "/metrics" ], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://elk:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "parcels", + "udpHost": "jaeger", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": [ "/", "/ping", "/metrics" ] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "parcels-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "hostnames": [ + "rabbitmq" + ] + }, + "redis": { + "connectionString": "redis", + "instance": "parcels:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "vault": { + "enabled": false, + "url": "http://vault:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": false, + "engineVersion": 2, + "mountPoint": "kv", + "path": "parcels-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "parcels-service", + "commonName": "parcels-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "orders-service", + "enabled": false, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } +} diff --git a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/development.appsettings.json b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/development.appsettings.json new file mode 100644 index 0000000..2307704 --- /dev/null +++ b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Api/SwiftParcel.Services.Parcels.Api/development.appsettings.json @@ -0,0 +1,198 @@ +{ + "app": { + "name": "SwiftParcel Parcels Service", + "service": "parcels-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://localhost:8500", + "service": "parcels-service", + "address": "localhost", + "port": "5007", + "pingEnabled": false, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": false, + "url": "http://localhost:9999", + "service": "parcels-service" + }, + "httpClient": { + "type": "direct", + "retries": 3, + "services": { + "pricing": "http://localhost:5008", + "lecturer-api": "http://localhost:5137" + }, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "logger": { + "level": "information", + "excludePaths": [ "/", "/ping", "/metrics" ], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "jaeger": { + "enabled": true, + "serviceName": "parcels", + "udpHost": "localhost", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": [ "/", "/ping", "/metrics" ] + }, + "jwt": { + "certificate": { + "location": "certs/localhost.cer" + }, + "validIssuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": true, + "validateLifetime": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://localhost:8086", + "database": "swiftparcel", + "env": "local", + "interval": 5 + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "parcels-service", + "seed": false + }, + "outbox": { + "enabled": false, + "type": "sequential", + "expiry": 3600, + "intervalMilliseconds": 2000, + "inboxCollection": "inbox", + "outboxCollection": "outbox", + "disableTransactions": true + }, + "rabbitMq": { + "connectionName": "parcels-service", + "retries": 3, + "retryInterval": 2, + "conventionsCasing": "snakeCase", + "logger": { + "enabled": true + }, + "username": "guest", + "password": "guest", + "virtualHost": "/", + "port": 5672, + "hostnames": [ + "localhost" + ], + "requestedConnectionTimeout": "00:00:30", + "requestedHeartbeat": "00:01:00", + "socketReadTimeout": "00:00:30", + "socketWriteTimeout": "00:00:30", + "continuationTimeout": "00:00:20", + "handshakeContinuationTimeout": "00:00:10", + "networkRecoveryInterval": "00:00:05", + "exchange": { + "declare": true, + "durable": true, + "autoDelete": false, + "type": "topic", + "name": "parcels" + }, + "queue": { + "declare": true, + "durable": true, + "exclusive": false, + "autoDelete": false, + "template": "parcels-service/{{exchange}}.{{message}}" + }, + "context": { + "enabled": true, + "header": "message_context" + }, + "spanContextHeader": "span_context" + }, + "redis": { + "connectionString": "localhost", + "instance": "parcels:" + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "vault": { + "enabled": false, + "url": "http://localhost:8200", + "authType": "token", + "token": "secret", + "username": "user", + "password": "secret", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "kv", + "path": "parcels-service/settings" + }, + "pki": { + "enabled": true, + "roleName": "parcels-service", + "commonName": "parcels-service.swiftparcel.io" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "orders-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb://{{username}}:{{password}}@localhost:27017" + } + } + } + } + } + \ No newline at end of file diff --git a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Application/SwiftParcel.Services.Parcels.Application/Commands/Handlers/AddParcelHandler.cs b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Application/SwiftParcel.Services.Parcels.Application/Commands/Handlers/AddParcelHandler.cs index f8ad72a..d31a88c 100644 --- a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Application/SwiftParcel.Services.Parcels.Application/Commands/Handlers/AddParcelHandler.cs +++ b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Application/SwiftParcel.Services.Parcels.Application/Commands/Handlers/AddParcelHandler.cs @@ -59,6 +59,7 @@ public async Task HandleAsync(AddParcel command, CancellationToken cancellationT } var createdAt = _dateTimeProvider.Now; var validTo = createdAt.AddMinutes(60); + var price = await _pricingServiceClient.GetParcelDeliveryPriceAsync(customerId ?? command.CustomerId, 0.0m, command.Width, command.Height, command.Depth, command.Weight, priority == Priority.High, command.AtWeekend, command.VipPackage); @@ -66,6 +67,7 @@ public async Task HandleAsync(AddParcel command, CancellationToken cancellationT { throw new PricingServiceException(command.ParcelId); } + var parcel = new Parcel(command.ParcelId, command.Description, command.Width, command.Height, command.Depth, command.Weight, priority, command.AtWeekend, pickupDate, deliveryDate, command.IsCompany, command.VipPackage, @@ -77,13 +79,16 @@ public async Task HandleAsync(AddParcel command, CancellationToken cancellationT parcel.SetDestinationAddress(command.DestinationStreet, command.DestinationBuildingNumber, command.DestinationApartmentNumber, command.DestinationCity, command.DestinationZipCode, command.DestinationCountry); - parcel.SetPriority(priority); - parcel.SetAtWeekend(command.AtWeekend); + parcel.SetIsCompany(command.IsCompany); + parcel.SetVipPackage(command.VipPackage); await _parcelRepository.AddAsync(parcel); - await _lecturerApiServiceClient.PostInquiryAsync(AddParcel.Generate(parcel)); + + var lecturerApiTask = _lecturerApiServiceClient.PostInquiryAsync(AddParcel.Generate(parcel)); + var timeoutTask = Task.Delay(TimeSpan.FromSeconds(20), cancellationToken); + await Task.WhenAny(lecturerApiTask, timeoutTask); await _messageBroker.PublishAsync(new ParcelAdded(command.ParcelId)); } diff --git a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Core/SwiftParcel.Services.Parcels.Core/Entities/Parcel.cs b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Core/SwiftParcel.Services.Parcels.Core/Entities/Parcel.cs index c8552e3..2c1d349 100644 --- a/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Core/SwiftParcel.Services.Parcels.Core/Entities/Parcel.cs +++ b/SwiftParcel.Services.Parcels/src/SwiftParcel.Services.Parcels.Core/SwiftParcel.Services.Parcels.Core/Entities/Parcel.cs @@ -205,6 +205,8 @@ public void CheckAddressZipCode(string element, string value) public void SetAtWeekend(bool atWeekend) => AtWeekend = atWeekend; - + public void SetIsCompany(bool isCompany) => IsCompany = isCompany; + + public void SetVipPackage(bool vipPackage) => VipPackage = vipPackage; } } diff --git a/SwiftParcel.Services.Pricing/Dockerfile b/SwiftParcel.Services.Pricing/Dockerfile index e90aa87..d58d47b 100644 --- a/SwiftParcel.Services.Pricing/Dockerfile +++ b/SwiftParcel.Services.Pricing/Dockerfile @@ -9,5 +9,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out . ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.Pricing.Api.dll"] diff --git a/SwiftParcel.Services.Pricing/scripts/dockerize.sh b/SwiftParcel.Services.Pricing/scripts/dockerize.sh new file mode 100755 index 0000000..3ebea67 --- /dev/null +++ b/SwiftParcel.Services.Pricing/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-pricing-service:latest . + +docker tag swift-parcel-pricing-service:latest adrianvsaint/swift-parcel-pricing-service:latest + +docker push adrianvsaint/swift-parcel-pricing-service diff --git a/SwiftParcel.Services.Pricing/src/SwiftParcel.Services.Pricing.Api/appsettings.production.json b/SwiftParcel.Services.Pricing/src/SwiftParcel.Services.Pricing.Api/appsettings.production.json new file mode 100644 index 0000000..2da4a67 --- /dev/null +++ b/SwiftParcel.Services.Pricing/src/SwiftParcel.Services.Pricing.Api/appsettings.production.json @@ -0,0 +1,135 @@ +{ + "app": { + "name": "SwiftParcel Pricing", + "service": "pricing-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "pricing-service", + "address": "pricing-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://fabio:9999", + "service": "pricing-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": { + "customers": "customers-service" + } + }, + "AllowedHosts": "*", + "vault": { + "enabled": false, + "url": "http://vault:8200", + "authType": "userpass", + "token": "secret", + "username": "user", + "password": "piotr-amadeusz-andrii-2023", + "dbusername": "andrii-courier-db-user", + "kv": { + "enabled": false, + "engineVersion": 2, + "mountPoint": "secret", + "path": "pricing-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "pricing-service", + "commonName": "pricing-service.swiftparcel.com" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "pricing-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority" + } + } + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "pacco", + "env": "docker", + "interval": 5 + }, + "jaeger": { + "enabled": true, + "serviceName": "pricing", + "udpHost": "jaeger", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "pricing-service", + "seed": false + }, + "rabbitMq": { + "hostnames": [ + "rabbitmq" + ] + } +} diff --git a/SwiftParcel.Services.Pricing/src/SwiftParcel.Services.Pricing.Api/development.appsettings.json b/SwiftParcel.Services.Pricing/src/SwiftParcel.Services.Pricing.Api/development.appsettings.json new file mode 100644 index 0000000..48f1cd4 --- /dev/null +++ b/SwiftParcel.Services.Pricing/src/SwiftParcel.Services.Pricing.Api/development.appsettings.json @@ -0,0 +1,160 @@ +{ + "app": { + "name": "SwiftParcel Pricing", + "service": "pricing-service", + "version": "1" + }, + "consul": { + "enabled": true, + "url": "http://localhost:8500", + "service": "pricing-service", + "address": "docker.for.win.localhost", + "port": "5013", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://localhost:9999", + "service": "pricing-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": { + "customers": "customers-service" + }, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "AllowedHosts": "*", + "vault": { + "enabled": false, + "url": "http://127.0.0.1:8200", + "authType": "userpass", + "token": "secret", + "username": "user", + "password": "piotr-amadeusz-andrii-2023", + "dbusername": "andrii-courier-db-user", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "secret", + "path": "pricing-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "pricing-service", + "commonName": "pricing-service.swiftparcel.com" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "pricing-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority" + } + } + } + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "pricing-service", + "seed": false + }, + "rabbitMq": { + "connectionName": "pricing-service", + "retries": 3, + "retryInterval": 2, + "conventionsCasing": "snakeCase", + "logger": { + "enabled": true + }, + "username": "guest", + "password": "guest", + "virtualHost": "/", + "port": 5672, + "hostnames": [ + "localhost" + ], + "requestedConnectionTimeout": "00:00:30", + "requestedHeartbeat": "00:01:00", + "socketReadTimeout": "00:00:30", + "socketWriteTimeout": "00:00:30", + "continuationTimeout": "00:00:20", + "handshakeContinuationTimeout": "00:00:10", + "networkRecoveryInterval": "00:00:05", + "exchange": { + "declare": true, + "durable": true, + "autoDelete": false, + "type": "topic", + "name": "identity" + }, + "queue": { + "declare": true, + "durable": true, + "exclusive": false, + "autoDelete": false, + "template": "pricing-service/{{exchange}}.{{message}}" + }, + "context": { + "enabled": true, + "header": "message_context" + }, + "spanContextHeader": "span_context" + } + } + \ No newline at end of file diff --git a/SwiftParcel.Web/Dockerfile b/SwiftParcel.Web/Dockerfile index d7e7183..4080d4d 100644 --- a/SwiftParcel.Web/Dockerfile +++ b/SwiftParcel.Web/Dockerfile @@ -1,20 +1,41 @@ -# Use the official Node.js 18 image as a base image -FROM node:18 +# # Use the official Node.js 18 image as a base image +# FROM node:18 -# Set the working directory in the container -WORKDIR /usr/src/app +# # Set the working directory in the container +# WORKDIR /usr/src/app -# Navigate to the frontend directory and copy package.json files -COPY ./frontend/package*.json ./ +# # Navigate to the frontend directory and copy package.json files +# COPY ./frontend/package*.json ./ + +# # Install the project dependencies +# RUN npm install --legacy-peer-deps + +# # Copy the rest of the frontend directory into the container +# COPY ./frontend/ . + +# # Make port 3001 available to the world outside this container +# EXPOSE 3001 + +# # Define the command to run your app using CMD which defines your runtime +# CMD ["npm", "start"] -# Install the project dependencies -RUN npm install --legacy-peer-deps -# Copy the rest of the frontend directory into the container +# Build stage +FROM node:18 as build-stage +WORKDIR /usr/src/app +COPY ./frontend/package*.json ./ COPY ./frontend/ . +RUN npm install --legacy-peer-deps +RUN npm run build + +# Production stage +FROM nginx:stable-alpine as production-stage +COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html + +# If you have a custom Nginx config, uncomment the next line and add your nginx.conf file +COPY ./nginx.conf /etc/nginx/nginx.conf -# Make port 3001 available to the world outside this container EXPOSE 3001 -# Define the command to run your app using CMD which defines your runtime -CMD ["npm", "start"] +CMD ["nginx", "-g", "daemon off;"] + diff --git a/SwiftParcel.Web/frontend/.env b/SwiftParcel.Web/frontend/.env new file mode 100644 index 0000000..df09d75 --- /dev/null +++ b/SwiftParcel.Web/frontend/.env @@ -0,0 +1 @@ +PORT=3001 diff --git a/SwiftParcel.Web/frontend/package-lock.json b/SwiftParcel.Web/frontend/package-lock.json index 00028e4..b3b76fe 100644 --- a/SwiftParcel.Web/frontend/package-lock.json +++ b/SwiftParcel.Web/frontend/package-lock.json @@ -5548,17 +5548,18 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", - "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==", - "dependencies": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/type-utils": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" }, @@ -5579,6 +5580,121 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/@typescript-eslint/experimental-utils": { "version": "5.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.44.0.tgz", @@ -5598,13 +5714,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz", - "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dependencies": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { @@ -5623,6 +5739,76 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", @@ -5640,12 +5826,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", - "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dependencies": { - "@typescript-eslint/typescript-estree": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -5665,6 +5851,121 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/@typescript-eslint/types": { "version": "5.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", @@ -21110,17 +21411,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/regexpu-core": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", @@ -28519,19 +28809,88 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "@typescript-eslint/eslint-plugin": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", - "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==", - "requires": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/type-utils": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "requires": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", - "regexpp": "^3.2.0", "semver": "^7.3.7", "tsutils": "^3.21.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + } } }, "@typescript-eslint/experimental-utils": { @@ -28543,14 +28902,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz", - "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "requires": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + } + } } }, "@typescript-eslint/scope-manager": { @@ -28563,14 +28961,82 @@ } }, "@typescript-eslint/type-utils": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", - "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "requires": { - "@typescript-eslint/typescript-estree": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + } + }, + "@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==" + }, + "@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "requires": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + } } }, "@typescript-eslint/types": { @@ -39333,11 +39799,6 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" - }, "regexpu-core": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", diff --git a/SwiftParcel.Web/frontend/package.json b/SwiftParcel.Web/frontend/package.json index a7d66c0..46504db 100644 --- a/SwiftParcel.Web/frontend/package.json +++ b/SwiftParcel.Web/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "version": "0.1.0", - "homepage": "https://nojusgat.github.io/parcel-delivery-system/#", + "homepage": "https://github.com/SaintAngeLs/courier_app", "private": true, "dependencies": { "@testing-library/jest-dom": "^5.16.5", @@ -32,7 +32,7 @@ "scripts": { "predeploy": "npm run build", "deploy": "gh-pages -d build", - "start": "react-scripts start", + "start": "HOST=0.0.0.0 react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/SwiftParcel.Web/frontend/public/index.html b/SwiftParcel.Web/frontend/public/index.html index d7e993b..605c4bc 100644 --- a/SwiftParcel.Web/frontend/public/index.html +++ b/SwiftParcel.Web/frontend/public/index.html @@ -25,7 +25,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + SwiftParcel diff --git a/SwiftParcel.Web/frontend/public/manifest.json b/SwiftParcel.Web/frontend/public/manifest.json index 080d6c7..e4aad80 100644 --- a/SwiftParcel.Web/frontend/public/manifest.json +++ b/SwiftParcel.Web/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "SwiftParcel App", + "name": "Create SwiftParcel Sample", "icons": [ { "src": "favicon.ico", diff --git a/SwiftParcel.Web/frontend/src/App.tsx b/SwiftParcel.Web/frontend/src/App.tsx index ea8e64f..c8864a9 100644 --- a/SwiftParcel.Web/frontend/src/App.tsx +++ b/SwiftParcel.Web/frontend/src/App.tsx @@ -1,19 +1,23 @@ import { HashRouter as Router, Routes, Route } from "react-router-dom"; import { RolesAuthRoute } from "./utils/others"; -import React, { Suspense, useEffect } from "react"; +import { Suspense } from "react"; import { Loader } from "./components/loader"; import Parcels from "./pages/parcels/parcels"; import ManageParcels from "./pages/parcels/manage"; import Register from "./pages/register"; import ManageParcelsCourier from "./pages/parcels/courier"; import Home from "./pages/home"; -import Deliveries from "./pages/deliveries"; import ManageCouriers from "./pages/couriers/manage"; import ManageCars from "./pages/cars/manage"; import ManageParcelsCar from "./pages/cars/parcels"; -import Inquiry from "./pages/inquiry"; -import Inquiries from "./pages/inquiries"; - +import CreateInquiry from "./pages/inquiries/createInquiry"; +import Inquiries from "./pages/inquiries/inquiries"; +import Offers from "./pages/offers/offersUser"; +import OfferRequests from "./pages/offers/offerRequests"; +import PendingOffers from "./pages/offers/pendingOffers"; +import Orders from "./pages/orders/orders"; +import YourDeliveries from "./pages/deliveries/yourDeliveries"; +import PendingDeliveries from "./pages/deliveries/pendingDeliveries"; export function App() { @@ -24,41 +28,81 @@ export function App() { } /> - + + } /> + } /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + + + } + /> + - + + } /> + } @@ -66,7 +110,7 @@ export function App() { + } @@ -74,7 +118,7 @@ export function App() { + } @@ -82,7 +126,7 @@ export function App() { + } @@ -90,7 +134,7 @@ export function App() { + } @@ -98,7 +142,7 @@ export function App() { + } diff --git a/SwiftParcel.Web/frontend/src/components/details/delivery.tsx b/SwiftParcel.Web/frontend/src/components/details/delivery.tsx new file mode 100644 index 0000000..75e0c6b --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/details/delivery.tsx @@ -0,0 +1,93 @@ +import { Table, Button} from "flowbite-react"; +import React from "react"; +import { DeliveryDetailsModal } from "../modals/deliveries/deliveryDetailsModal"; +import booleanToString from "../parsing/booleanToString"; +import dateFromUTCToLocal from "../parsing/dateFromUTCToLocal"; +import formatDeliveryStatus from "../parsing/formatDeliveryStatus"; + +interface DeliveryDetailsProps { + deliveryData: any; + pageContent: string; +} + +export function DeliveryDetails({ + deliveryData, pageContent +}: DeliveryDetailsProps) { + + const [delivery, setDelivery] = React.useState(deliveryData); + + const [showDeliveryDetailsModal, setShowDeliveryDetailsModal] = React.useState(false); + + const formatAddressFirstLine = (address : any) => { + if (address.apartmentNumber.length === 0) { + return `${address.street} ${address.buildingNumber}`; + } + else { + return `${address.street} ${address.buildingNumber}/${address.apartmentNumber}`; + } + } + + const formatAddressSecondLine = (address : any) => { + return `${address.zipCode} ${address.city}, ${address.country}`; + } + + const formatDateCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(0, 10); + }; + + const formatTimeCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(11, 19); + }; + + return ( + <> + + {delivery.id} + + + {formatAddressFirstLine(delivery.source)} + {formatAddressSecondLine(delivery.source)} + + + + + {formatAddressFirstLine(delivery.destination)} + {formatAddressSecondLine(delivery.destination)} + + + + + {formatDateCreatedAt(delivery.pickupDate)} + + + + + {formatDateCreatedAt(delivery.deliveryDate)} + + + {delivery.priority} + {booleanToString(delivery.atWeekend)} + {formatDeliveryStatus(delivery.status)} + + + {formatDateCreatedAt(delivery.lastUpdate)} + {formatTimeCreatedAt(delivery.lastUpdate)} + + + + + + + + + + ); +} diff --git a/SwiftParcel.Web/frontend/src/components/details/inquiry.tsx b/SwiftParcel.Web/frontend/src/components/details/inquiry.tsx new file mode 100644 index 0000000..276ceb2 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/details/inquiry.tsx @@ -0,0 +1,85 @@ +import { Table, Button} from "flowbite-react"; +import React from "react"; +import { InquiryDetailsModal } from "../modals/inquiries/inquiryDetailsModal"; +import dateFromUTCToLocal from "../parsing/dateFromUTCToLocal"; + +interface InquiryDetailsProps { + inquiryData: any; +} + +export function isPackageValid(validTo : string) { + const todayDate = new Date(); + const validDate = new Date(validTo); + return todayDate < validDate; +}; + +export function InquiryDetails({ + inquiryData +}: InquiryDetailsProps) { + + const [inquiry, setInquiry] = React.useState(inquiryData); + + const [showInquiryDetailsModal, setShowInquiryDetailsModal] = React.useState(false); + + const formatAddressFirstLine = (address : any) => { + if (address.apartmentNumber.length === 0) { + return `${address.street} ${address.buildingNumber}`; + } + else { + return `${address.street} ${address.buildingNumber}/${address.apartmentNumber}`; + } + } + + const formatAddressSecondLine = (address : any) => { + return `${address.zipCode} ${address.city}, ${address.country}`; + } + + const formatDateCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(0, 10); + }; + + const formatTimeCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(11, 19); + }; + + return ( + <> + + {inquiry.id} + {inquiry.width} cm x {inquiry.height} cm x {inquiry.depth} cm + {inquiry.weight} kg + + + {formatAddressFirstLine(inquiry.source)} + {formatAddressSecondLine(inquiry.source)} + + + + + {formatAddressFirstLine(inquiry.destination)} + {formatAddressSecondLine(inquiry.destination)} + + + + + {formatDateCreatedAt(inquiry.createdAt)} + {formatTimeCreatedAt(inquiry.createdAt)} + + + {isPackageValid(inquiry.validTo) ? "valid" : "expired"} + + + + + + + + ); +} diff --git a/SwiftParcel.Web/frontend/src/components/details/offer.tsx b/SwiftParcel.Web/frontend/src/components/details/offer.tsx new file mode 100644 index 0000000..1204957 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/details/offer.tsx @@ -0,0 +1,59 @@ +import { Table, Button} from "flowbite-react"; +import React from "react"; +import { UserDetailsModal } from "../modals/offers/userDetailsModal"; +import dateFromUTCToLocal from "../parsing/dateFromUTCToLocal"; +import { getUserIdFromStorage } from "../../utils/storage"; + +interface OfferDetailsProps { + offerData: any; + userData: any; +} + +export function OfferDetails({ + offerData, userData +}: OfferDetailsProps) { + + const [offer, setOffer] = React.useState(offerData); + + const [showUserDetailsModal, setShowUserDetailsModal] = React.useState(false); + + const formatDateCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(0, 10); + }; + + const formatTimeCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(11, 19); + }; + + return ( + <> + + {offer.parcelId} + {offer.totalPrice} + + + {formatDateCreatedAt(offer.expiringAt)} + {formatTimeCreatedAt(offer.expiringAt)} + + + {offer.companyName} + + + + + + { (userData !== null) ? + + : null } + + ); +} diff --git a/SwiftParcel.Web/frontend/src/components/details/offerRequest.tsx b/SwiftParcel.Web/frontend/src/components/details/offerRequest.tsx new file mode 100644 index 0000000..7a50a4c --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/details/offerRequest.tsx @@ -0,0 +1,93 @@ +import { Table, Button} from "flowbite-react"; +import React from "react"; +import { InquiryDetailsModal } from "../modals/inquiries/inquiryDetailsModal"; +import { OfferRequestDetailsModal } from "../modals/offers/offerRequestDetailsModal"; +import dateFromUTCToLocal from "../parsing/dateFromUTCToLocal"; +import formatOfferStatus from "../parsing/formatOfferStatus"; + +interface OfferRequestDetailsProps { + offerRequestData: any; + pageContent: string; +} + +export function OfferRequestDetails({ + offerRequestData, pageContent +}: OfferRequestDetailsProps) { + + const [offerRequest, setOfferRequest] = React.useState(offerRequestData); + + const [showInquiryDetailsModal, setShowInquiryDetailsModal] = React.useState(false); + const [showOfferRequestDetailsModal, setShowOfferRequestDetailsModal] = React.useState(false); + + const formatAddressFirstLine = (address : any) => { + if (address.apartmentNumber.length === 0) { + return `${address.street} ${address.buildingNumber}`; + } + else { + return `${address.street} ${address.buildingNumber}/${address.apartmentNumber}`; + } + } + + const formatAddressSecondLine = (address : any) => { + return `${address.zipCode} ${address.city}, ${address.country}`; + } + + const formatDateCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(0, 10); + }; + + const formatTimeCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(11, 19); + }; + + return ( + <> + + {offerRequest.id} + + + + {formatOfferStatus(offerRequest.status)} + + + {formatDateCreatedAt(offerRequest.orderRequestDate)} + {formatTimeCreatedAt(offerRequest.orderRequestDate)} + + + + + {formatDateCreatedAt(offerRequest.requestValidTo)} + {formatTimeCreatedAt(offerRequest.requestValidTo)} + + + {offerRequest.buyerName} + {offerRequest.buyerEmail} + + + {formatAddressFirstLine(offerRequest.buyerAddress)} + {formatAddressSecondLine(offerRequest.buyerAddress)} + + + + + + + + + + + + ); +} diff --git a/SwiftParcel.Web/frontend/src/components/details/order.tsx b/SwiftParcel.Web/frontend/src/components/details/order.tsx new file mode 100644 index 0000000..ca35b32 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/details/order.tsx @@ -0,0 +1,97 @@ +import { Table, Button} from "flowbite-react"; +import React from "react"; +import { InquiryDetailsModal } from "../modals/inquiries/inquiryDetailsModal"; +import { OrderDetailsModal } from "../modals/orders/orderDetailsModal"; +import dateFromUTCToLocal from "../parsing/dateFromUTCToLocal"; +import formatOfferStatus from "../parsing/formatOfferStatus"; + +interface OrderDetailsProps { + orderData: any; +} + +export function isPackageValid(validTo : string) { + const todayDate = new Date(); + const validDate = new Date(validTo); + return todayDate < validDate; +}; + +export function OrderDetails({ + orderData +}: OrderDetailsProps) { + + const [order, setOrder] = React.useState(orderData); + + const [showInquiryDetailsModal, setShowInquiryDetailsModal] = React.useState(false); + const [showOrderDetailsModal, setShowOrderDetailsModal] = React.useState(false); + + const formatAddressFirstLine = (address : any) => { + if (address.apartmentNumber.length === 0) { + return `${address.street} ${address.buildingNumber}`; + } + else { + return `${address.street} ${address.buildingNumber}/${address.apartmentNumber}`; + } + } + + const formatAddressSecondLine = (address : any) => { + return `${address.zipCode} ${address.city}, ${address.country}`; + } + + const formatDateCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(0, 10); + }; + + const formatTimeCreatedAt = (utcCreatedAt: string) => { + return dateFromUTCToLocal(utcCreatedAt).substring(11, 19); + }; + + return ( + <> + + {order.id} + + + + + + {formatAddressFirstLine(order.parcel.source)} + {formatAddressSecondLine(order.parcel.source)} + + + + + {formatAddressFirstLine(order.parcel.destination)} + {formatAddressSecondLine(order.parcel.destination)} + + + {order.courierCompany} + + + {formatDateCreatedAt(order.orderRequestDate)} + {formatTimeCreatedAt(order.orderRequestDate)} + + + {formatOfferStatus(order.status)} + + + + + + + + + + + ); +} diff --git a/SwiftParcel.Web/frontend/src/components/header.tsx b/SwiftParcel.Web/frontend/src/components/header.tsx index 63e3966..218d5f1 100644 --- a/SwiftParcel.Web/frontend/src/components/header.tsx +++ b/SwiftParcel.Web/frontend/src/components/header.tsx @@ -6,20 +6,33 @@ import { FaShippingFast } from "react-icons/fa"; import { CgProfile } from "react-icons/cg"; import { getUserInfo, saveUserInfo } from "../utils/storage"; import { LoginModal } from "./modals/loginModal"; -import React from "react"; +import React, { useState } from "react"; import { getProfile, logout } from "../utils/api"; import AppNavLink from "./appNavLink"; +import { getUserIdFromStorage } from "../utils/storage"; export function Header(props: { loading: boolean; setLoading: (loading: boolean) => void; + showLoginModal?: boolean; + setShowLoginModal?: (loading: boolean) => void; }) { + const [modal, setModal] = React.useState(1); const [showLoginModal, setShowLoginModal] = React.useState(false); const navigate = useNavigate(); + const [userInfo, setUserInfo] = React.useState(null); + const [userEmail, setUserEmail] = useState(''); + const [userRole, setUserRole] = useState(''); + const [userToken, setUserToken] = React.useState(false); - const [isCourier, setIsCourier] = React.useState(false); + + React.useEffect(() => { + if (getUserIdFromStorage() === null) { + setShowLoginModal(props.showLoginModal); + } + }, [modal]); React.useEffect(() => { setUserToken(getUserInfo()); @@ -33,12 +46,15 @@ export function Header(props: { if (userToken) { getProfile() .then((res) => { + console.log("res", res); + setUserEmail(res.email); + setUserRole(res.role); + if (res?.status === 200) { - const newUserInfo = { ...getUserInfo(), courier: res.data.courier }; + + const newUserInfo = { ...getUserInfo(), courier: res.courier }; saveUserInfo(newUserInfo); - if (res.data.courier) { - setIsCourier(true); - } + } else { throw new Error(); } @@ -53,7 +69,7 @@ export function Header(props: { }); } else if (userToken === null) { props.setLoading(false); - setIsCourier(false); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [userToken]); @@ -65,6 +81,43 @@ export function Header(props: { navigate("/"); }; + + const renderNavLinks = () => { + switch (userRole) { + case 'courier': + return ( + <> + + + + ); + case 'officeworker': + return ( + <> + + + + + ); + case 'user': + return ( + <> + + + + + + ); + default: + return ( + <> + + + + ); + } + }; + return ( <> @@ -86,7 +139,7 @@ export function Header(props: { {userToken?.user?.username} - {userToken?.user?.email} + {userEmail || 'Email not available'} Sign out @@ -102,21 +155,7 @@ export function Header(props: { ) : null} - - {userToken?.user?.role === "User" ? <> : null} - {isCourier ? ( - <> - - - - ) : null} - {userToken?.user?.role === "admin" ? ( - <> - - - - - ) : null} + {renderNavLinks()} diff --git a/SwiftParcel.Web/frontend/src/components/modals/deliveries/deliveryDetailsModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/deliveries/deliveryDetailsModal.tsx new file mode 100644 index 0000000..c389cb1 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/deliveries/deliveryDetailsModal.tsx @@ -0,0 +1,341 @@ +import { + Button, + Label, + Modal, + TextInput, + } from "flowbite-react"; +import React from "react"; +import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; +import formatDeliveryStatus from "../../parsing/formatDeliveryStatus"; +import { assignCourierToDelivery, pickupDelivery, completeDelivery, failDelivery } from "../../../utils/api"; +import booleanToString from "../../parsing/booleanToString"; +import { getUserIdFromStorage } from "../../../utils/storage"; + + interface DeliveryDetailsModalProps { + show: boolean; + setShow: (show: boolean) => void; + delivery: any; + pageContent: string; + } + + const formatDate = (date: string) => { + return `${date.substring(0, 10)}`; + }; + + const formatDateToUTC = (date: string) => { + const utcDate = dateFromUTCToLocal(date); + return `${utcDate.substring(0, 10)} ${utcDate.substring(11, 19)}`; + }; + + const LabelsWithBorder = ({ idA, valueA, idB, valueB }) => ( +
+
+ ); + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const IdInfoDetailsSection = ({ detailsData }) => ( +
+ + +
+ ); + + const StatusDetailsSection = ({ detailsData, assign, pickup, complete, fail, failed, setFailed, reason, setReason, finalized, refresh }) => ( +
+ + { detailsData.delivery.status === "cannotdeliver" ? +
+ + +
+ : null } + + + { (detailsData.pageContent == "pending-deliveries") ? ( +
+ +
+ ) : null } + + { (detailsData.pageContent == "your-deliveries") ? ( +
+ { detailsData.delivery.status === "assigned" ? +
+ +
+ : null} + { detailsData.delivery.status === "inprogress" ? +
+ + +
+ : null} +
+ ) : null } + + { failed ? ( +
+ + setReason(e.target.value)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md`} + /> + +
+ ) : null } + + { finalized ? ( +
+ +
+ ) : null } +
+ ); + + const OrderDetailsSection = ({ detailsData }) => ( +
+ + + + +
+ ); + + const PackageInfoDetailsSection = ({ detailsData }) => ( +
+ + +
+ ); + + const AddressDetailsSection = ({ prefix, detailsData }) => ( +
+ + + + + + +
+ ); + + export function DeliveryDetailsModal(props: DeliveryDetailsModalProps) { + const close = () => { + setReason(""); + setFailed(false); + setFinalized(false); + props.setShow(false); + }; + + const submit = async (e: any) => { + e.preventDefault(); + close(); + }; + + const [failed, setFailed] = React.useState(false); + const [finalized, setFinalized] = React.useState(false); + + const [reason, setReason] = React.useState(""); + + const assign = () => { + assignCourierToDelivery(props.delivery.id, getUserIdFromStorage()); + pickupDelivery(props.delivery.id); + setFinalized(true); + }; + + const pickup = () => { + pickupDelivery(props.delivery.id); + setFinalized(true); + }; + + const complete = () => { + completeDelivery(props.delivery.id, new Date().toJSON()); + setFinalized(true); + }; + + const fail = () => { + failDelivery(props.delivery.id, new Date().toJSON(), reason); + setFinalized(true); + }; + + const refresh = () => { + close(); + window.location.reload(); + }; + + return ( + + + + +
+
+

+ Details of delivery: +

+
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/deliveries/filterDeliveriesModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/deliveries/filterDeliveriesModal.tsx new file mode 100644 index 0000000..887758e --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/deliveries/filterDeliveriesModal.tsx @@ -0,0 +1,587 @@ +import { + Alert, + Button, + Label, + Modal, + Spinner, + TextInput, + } from "flowbite-react"; + import React from "react"; + import { HiInformationCircle } from "react-icons/hi"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + import booleanToString from "../../parsing/booleanToString"; + + interface FilterDeliveriesModalProps { + show: boolean; + setShow: (show: boolean) => void; + inputData: any; + tableData: any; + setTableData: any; + pageContent: string; + } + + type FilteringDetails = { + keywordId: string; + keywordOrderId: string; + filterStatus: string; + minDeliveryAttemptDate: string; + maxDeliveryAttemptDate: string; + keywordCannotDeliverReason: string; + minLastUpdate: string; + maxLastUpdate: string; + minPickupDate: string; + maxPickupDate: string; + minDeliveryDate: string; + maxDeliveryDate: string; + filterPriority: string; + filterAtWeekend: string; + minVolume: number; + maxVolume: number; + minWeight: number; + maxWeight: number; + keywordSourceStreet: string; + keywordSourceBuildingNumber: string; + keywordSourceApartmentNumber: string; + keywordSourceCity: string; + keywordSourceZipCode: string; + keywordSourceCountry: string; + keywordDestinationStreet: string; + keywordDestinationBuildingNumber: string; + keywordDestinationApartmentNumber: string; + keywordDestinationCity: string; + keywordDestinationZipCode: string; + keywordDestinationCountry: string; + }; + + const TextInputWithLabel = ({ id, label, value, onChange, type}) => ( +
+ + onChange(e)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md`} + /> +
+ ); + + const DateInputWithLabel = ({ id, label, value, onChange }) => { + // Function to format the date to MongoDB format + const formatToMongoDBDate = (dateString : string) => { + const date = new Date(dateString); + return date.toISOString(); + }; + + // Function to handle date change + const handleDateChange = (e : any) => { + const formattedDate = formatToMongoDBDate(e.target.value); + onChange(formattedDate); + }; + + return ( +
+ + +
+ ); + }; + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const IdInfoFilterSection = ({ filterData, handleStringChange }) => ( +
+ + +
+ ); + + const StatusInfoFilterSection = ({ filterData, handleStringChange, handleDateChange }) => ( +
+
+
+ + + + + + + + +
+ ); + + const OrderInfoFilterSection = ({ filterData, handleDateChange, handleStringChange }) => ( +
+ + + + + + +
+ + +
+ +
+ + +
+ +
+ ); + + const PackageInfoFilterSection = ({ filterData, handleNumberChange }) => ( +
+ + + + + +
+ ); + + const AddressFilterSection = ({ prefix, filterData, handleStringChange }) => ( +
+ + + + + + + + +
+ ); + + export function FilterDeliveriesModal(props: FilterDeliveriesModalProps) { + const close = () => { + setError(""); + setIsLoading(false); + props.setShow(false); + }; + + const minDefNum = 0; + const maxDefNum = 99999; + + const [filteringDetails, setFilteringDetails] = React.useState({ + keywordId: "", + keywordOrderId: "", + filterStatus: "all", + minDeliveryAttemptDate: "", + maxDeliveryAttemptDate: "", + keywordCannotDeliverReason: "", + minLastUpdate: "", + maxLastUpdate: "", + minPickupDate: "", + maxPickupDate: "", + minDeliveryDate: "", + maxDeliveryDate: "", + filterPriority: "all", + filterAtWeekend: "all", + minVolume: minDefNum, + maxVolume: maxDefNum, + minWeight: minDefNum, + maxWeight: maxDefNum, + keywordSourceStreet: "", + keywordSourceBuildingNumber: "", + keywordSourceApartmentNumber: "", + keywordSourceCity: "", + keywordSourceZipCode: "", + keywordSourceCountry: "", + keywordDestinationStreet: "", + keywordDestinationBuildingNumber: "", + keywordDestinationApartmentNumber: "", + keywordDestinationCity: "", + keywordDestinationZipCode: "", + keywordDestinationCountry: "" + }); + + const handleNumberChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = parseFloat(event.target.value); + setFilteringDetails(prevState => ({ + ...prevState, + [field]: isNaN(newValue) ? 0 : newValue + })); + }; + + const handleStringChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handleDateChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const [isLoading, setIsLoading] = React.useState(false); + const [error, setError] = React.useState(""); + + const clearDetails = () => { + setFilteringDetails({ + keywordId: "", + keywordOrderId: "", + filterStatus: "all", + minDeliveryAttemptDate: "", + maxDeliveryAttemptDate: "", + keywordCannotDeliverReason: "", + minLastUpdate: "", + maxLastUpdate: "", + minPickupDate: "", + maxPickupDate: "", + minDeliveryDate: "", + maxDeliveryDate: "", + filterPriority: "all", + filterAtWeekend: "all", + minVolume: minDefNum, + maxVolume: maxDefNum, + minWeight: minDefNum, + maxWeight: maxDefNum, + keywordSourceStreet: "", + keywordSourceBuildingNumber: "", + keywordSourceApartmentNumber: "", + keywordSourceCity: "", + keywordSourceZipCode: "", + keywordSourceCountry: "", + keywordDestinationStreet: "", + keywordDestinationBuildingNumber: "", + keywordDestinationApartmentNumber: "", + keywordDestinationCity: "", + keywordDestinationZipCode: "", + keywordDestinationCountry: "" + }); + }; + + const submit = async (e: any) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + FilterOfferRequests(); + + close(); + setIsLoading(false); + }; + + const FilterOfferRequests = () => { + const filteredElements = props.inputData.filter((element : any) => + // filtering of id info section + (filteringDetails.keywordId == "" || element.id.toLowerCase().includes(filteringDetails.keywordId.toLowerCase())) && + (filteringDetails.keywordOrderId == "" || element.orderId.toLowerCase().includes(filteringDetails.keywordOrderId.toLowerCase())) && + + // filtering of status info section + (filteringDetails.filterStatus == "all" || element.status == filteringDetails.filterStatus) && + (filteringDetails.keywordCannotDeliverReason == "" || element.cannotDeliverReason?.toLowerCase().includes(filteringDetails.keywordCannotDeliverReason.toLowerCase())) && + + (filteringDetails.minDeliveryAttemptDate == "" || new Date(dateFromUTCToLocal(element?.deliveryAttemptDate)) >= new Date(filteringDetails.minDeliveryAttemptDate)) && + (filteringDetails.maxDeliveryAttemptDate == "" || new Date(dateFromUTCToLocal(element?.deliveryAttemptDate)) <= new Date(filteringDetails.maxDeliveryAttemptDate)) && + + (filteringDetails.minLastUpdate == "" || new Date(dateFromUTCToLocal(element.lastUpdate)) >= new Date(filteringDetails.minLastUpdate)) && + (filteringDetails.maxLastUpdate == "" || new Date(dateFromUTCToLocal(element.lastUpdate)) <= new Date(filteringDetails.maxLastUpdate)) && + + // filtering of order info section + (filteringDetails.minPickupDate == "" || new Date(dateFromUTCToLocal(element.pickupDate)) >= new Date(filteringDetails.minPickupDate)) && + (filteringDetails.maxPickupDate == "" || new Date(dateFromUTCToLocal(element.pickupDate)) <= new Date(filteringDetails.maxPickupDate)) && + + (filteringDetails.minDeliveryDate == "" || new Date(dateFromUTCToLocal(element.deliveryDate)) >= new Date(filteringDetails.minDeliveryDate)) && + (filteringDetails.maxDeliveryDate == "" || new Date(dateFromUTCToLocal(element.deliveryDate)) <= new Date(filteringDetails.maxDeliveryDate)) && + + (filteringDetails.filterPriority == "all" || (filteringDetails.filterPriority == element.priority)) && + (filteringDetails.filterAtWeekend == "all" || (filteringDetails.filterAtWeekend == booleanToString(element.atWeekend))) && + + // filtering of package info section + (filteringDetails.minVolume == null || element.volume >= filteringDetails.minVolume) && + (filteringDetails.maxVolume == null || element.volume <= filteringDetails.maxVolume) && + + (filteringDetails.minWeight == null || element.weight >= filteringDetails.minWeight) && + (filteringDetails.maxWeight == null || element.weight <= filteringDetails.maxWeight) && + + // filtering of source address section + (filteringDetails.keywordSourceStreet == "" || element.source.street.toLowerCase().includes(filteringDetails.keywordSourceStreet.toLowerCase())) && + (filteringDetails.keywordSourceBuildingNumber == "" || element.source.buildingNumber.toLowerCase().includes(filteringDetails.keywordSourceBuildingNumber.toLowerCase())) && + + (filteringDetails.keywordSourceApartmentNumber == "" || element.source.apartmentNumber.toLowerCase().includes(filteringDetails.keywordSourceApartmentNumber.toLowerCase())) && + (filteringDetails.keywordSourceCity == "" || element.source.city.toLowerCase().includes(filteringDetails.keywordSourceCity.toLowerCase())) && + + (filteringDetails.keywordSourceZipCode == "" || element.source.zipCode.toLowerCase().includes(filteringDetails.keywordSourceZipCode.toLowerCase())) && + (filteringDetails.keywordSourceCountry == "" || element.source.country.toLowerCase().includes(filteringDetails.keywordSourceCountry.toLowerCase())) && + + // filtering of destination address section + (filteringDetails.keywordDestinationStreet == "" || element.destination.street.toLowerCase().includes(filteringDetails.keywordDestinationStreet.toLowerCase())) && + (filteringDetails.keywordDestinationBuildingNumber == "" || element.destination.buildingNumber.toLowerCase().includes(filteringDetails.keywordDestinationBuildingNumber.toLowerCase())) && + + (filteringDetails.keywordDestinationApartmentNumber == "" || element.destination.apartmentNumber.toLowerCase().includes(filteringDetails.keywordDestinationApartmentNumber.toLowerCase())) && + (filteringDetails.keywordDestinationCity == "" || element.destination.city.toLowerCase().includes(filteringDetails.keywordDestinationCity.toLowerCase())) && + + (filteringDetails.keywordDestinationZipCode == "" || element.destination.zipCode.toLowerCase().includes(filteringDetails.keywordDestinationZipCode.toLowerCase())) && + (filteringDetails.keywordDestinationCountry == "" || element.destination.country.toLowerCase().includes(filteringDetails.keywordDestinationCountry.toLowerCase())) + ); + + props.setTableData(filteredElements); + }; + + return ( + + + + +
+
+

+ Filter {props.pageContent == "your-deliveries" ? 'your' : 'pending'} deliveries by attributes: +

+ {error ? ( + + {error} + + ) : null} +
+
+ +
+ +
+
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ +
+ {isLoading ? ( + + ) : ( + + )} +
+ +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/inquiries/filterInquiriesModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/inquiries/filterInquiriesModal.tsx new file mode 100644 index 0000000..68828b4 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/inquiries/filterInquiriesModal.tsx @@ -0,0 +1,691 @@ +import { + Alert, + Button, + Label, + Modal, + Spinner, + TextInput, + } from "flowbite-react"; + import React from "react"; + import { HiInformationCircle } from "react-icons/hi"; + import { isPackageValid } from "../../details/inquiry"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + import booleanToString from "../../parsing/booleanToString"; + + interface FilterInquiriesModalProps { + show: boolean; + setShow: (show: boolean) => void; + inputData: any; + tableData: any; + setTableData: any; + role: string; + } + + type FilteringDetails = { + keywordId: string; + keywordCustomerId: string; + keywordDescription: string; + minWidth: number; + maxWidth: number; + minHeight: number; + maxHeight: number; + minDepth: number; + maxDepth: number; + minWeight: number; + maxWeight: number; + keywordSourceStreet: string; + keywordSourceBuildingNumber: string; + keywordSourceApartmentNumber: string; + keywordSourceCity: string; + keywordSourceZipCode: string; + keywordSourceCountry: string; + keywordDestinationStreet: string; + keywordDestinationBuildingNumber: string; + keywordDestinationApartmentNumber: string; + keywordDestinationCity: string; + keywordDestinationZipCode: string; + keywordDestinationCountry: string; + minDateOfInquiring: string; + maxDateOfInquiring: string; + minPickupDate: string; + maxPickupDate: string; + minDeliveryDate: string; + maxDeliveryDate: string; + filterStatus: string; + filterPriority: string; + filterAtWeekend: string; + filterIsCompany: string; + filterVipPackage: string; + }; + + const TextInputWithLabel = ({ id, label, value, onChange, type}) => ( +
+ + onChange(e)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md`} + /> +
+ ); + + const DateInputWithLabel = ({ id, label, value, onChange }) => { + // Function to format the date to MongoDB format + const formatToMongoDBDate = (dateString : string) => { + const date = new Date(dateString); + return date.toISOString(); + }; + + // Function to handle date change + const handleDateChange = (e : any) => { + const formattedDate = formatToMongoDBDate(e.target.value); + onChange(formattedDate); + }; + + return ( +
+ + +
+ ); + }; + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const IdFilterSection = ({ filterData, handleStringChange, role }) => ( +
+ + {(role === "officeworker") ? + + : null } + +
+ ); + + const DimensionsFilterSection = ({ filterData, handleNumberChange }) => ( +
+ + + + + + + + +
+ ); + + const WeightFilterSection = ({ filterData, handleNumberChange }) => ( +
+ + +
+ ); + + const AddressFilterSection = ({ prefix, filterData, handleStringChange }) => ( +
+ + + + + + + + +
+ ); + + const DateOfInquiringFilterSection = ({ filterData, handleDateChange }) => ( +
+ + +
+ ); + + const PickupDateFilterSection = ({ filterData, handleDateChange }) => ( +
+ + +
+ ); + + const DeliveryDateFilterSection = ({ filterData, handleDateChange }) => ( +
+ + +
+ ); + + const StatusFilterSection = ({ filterData, handleStringChange }) => ( +
+ +
+ ); + + const AdditionalInfoFilterSection = ({ filterData, handleStringChange }) => ( +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ ); + + export function FilterInquiriesModal(props: FilterInquiriesModalProps) { + const close = () => { + setError(""); + setIsLoading(false); + props.setShow(false); + }; + + const minDefNum = 0; + const maxDefNum = 99999; + + const [filteringDetails, setFilteringDetails] = React.useState({ + keywordId: "", + keywordCustomerId: "", + keywordDescription: "", + minWidth: minDefNum, + maxWidth: maxDefNum, + minHeight: minDefNum, + maxHeight: maxDefNum, + minDepth: minDefNum, + maxDepth: maxDefNum, + minWeight: minDefNum, + maxWeight: maxDefNum, + keywordSourceStreet: "", + keywordSourceBuildingNumber: "", + keywordSourceApartmentNumber: "", + keywordSourceCity: "", + keywordSourceZipCode: "", + keywordSourceCountry: "", + keywordDestinationStreet: "", + keywordDestinationBuildingNumber: "", + keywordDestinationApartmentNumber: "", + keywordDestinationCity: "", + keywordDestinationZipCode: "", + keywordDestinationCountry: "", + minDateOfInquiring: "", + maxDateOfInquiring: "", + minPickupDate: "", + maxPickupDate: "", + minDeliveryDate: "", + maxDeliveryDate: "", + filterStatus: "all", + filterPriority: "all", + filterAtWeekend: "all", + filterIsCompany: "all", + filterVipPackage: "all" + }); + + const handleNumberChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = parseFloat(event.target.value); + setFilteringDetails(prevState => ({ + ...prevState, + [field]: isNaN(newValue) ? 0 : newValue + })); + }; + + const handleStringChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handleDateChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const [isLoading, setIsLoading] = React.useState(false); + const [error, setError] = React.useState(""); + + const clearDetails = () => { + setFilteringDetails({ + keywordId: "", + keywordCustomerId: "", + keywordDescription: "", + minWidth: minDefNum, + maxWidth: maxDefNum, + minHeight: minDefNum, + maxHeight: maxDefNum, + minDepth: minDefNum, + maxDepth: maxDefNum, + minWeight: minDefNum, + maxWeight: maxDefNum, + keywordSourceStreet: "", + keywordSourceBuildingNumber: "", + keywordSourceApartmentNumber: "", + keywordSourceCity: "", + keywordSourceZipCode: "", + keywordSourceCountry: "", + keywordDestinationStreet: "", + keywordDestinationBuildingNumber: "", + keywordDestinationApartmentNumber: "", + keywordDestinationCity: "", + keywordDestinationZipCode: "", + keywordDestinationCountry: "", + minDateOfInquiring: "", + maxDateOfInquiring: "", + minPickupDate: "", + maxPickupDate: "", + minDeliveryDate: "", + maxDeliveryDate: "", + filterStatus: "all", + filterPriority: "all", + filterAtWeekend: "all", + filterIsCompany: "all", + filterVipPackage: "all" + }); + }; + + const submit = async (e: any) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + filterInquiries(); + + close(); + setIsLoading(false); + }; + + const filterInquiries = () => { + const filteredElements = props.inputData.filter((element : any) => + // filtering of id section + (filteringDetails.keywordId == "" || element.id.toLowerCase().includes(filteringDetails.keywordId.toLowerCase())) && + (filteringDetails.keywordCustomerId == "" || element.customerId?.toLowerCase().includes(filteringDetails.keywordCustomerId.toLowerCase())) && + (filteringDetails.keywordDescription == "" || element.description.toLowerCase().includes(filteringDetails.keywordDescription.toLowerCase())) && + + // filtering of dimensions section + (filteringDetails.minWidth == null || element.width >= filteringDetails.minWidth) && + (filteringDetails.maxWidth == null || element.width <= filteringDetails.maxWidth) && + + (filteringDetails.minHeight == null || element.height >= filteringDetails.minHeight) && + (filteringDetails.maxHeight == null || element.height <= filteringDetails.maxHeight) && + + (filteringDetails.minDepth == null || element.depth >= filteringDetails.minDepth) && + (filteringDetails.maxDepth == null || element.depth <= filteringDetails.maxDepth) && + + // filtering of weight section + (filteringDetails.minWeight == null || element.weight >= filteringDetails.minWeight) && + (filteringDetails.maxWeight == null || element.weight <= filteringDetails.maxWeight) && + + // filtering of source address section + (filteringDetails.keywordSourceStreet == "" || element.source.street.toLowerCase().includes(filteringDetails.keywordSourceStreet.toLowerCase())) && + (filteringDetails.keywordSourceBuildingNumber == "" || element.source.buildingNumber.toLowerCase().includes(filteringDetails.keywordSourceBuildingNumber.toLowerCase())) && + + (filteringDetails.keywordSourceApartmentNumber == "" || element.source.apartmentNumber.toLowerCase().includes(filteringDetails.keywordSourceApartmentNumber.toLowerCase())) && + (filteringDetails.keywordSourceCity == "" || element.source.city.toLowerCase().includes(filteringDetails.keywordSourceCity.toLowerCase())) && + + (filteringDetails.keywordSourceZipCode == "" || element.source.zipCode.toLowerCase().includes(filteringDetails.keywordSourceZipCode.toLowerCase())) && + (filteringDetails.keywordSourceCountry == "" || element.source.country.toLowerCase().includes(filteringDetails.keywordSourceCountry.toLowerCase())) && + + // filtering of destination address section + (filteringDetails.keywordDestinationStreet == "" || element.destination.street.toLowerCase().includes(filteringDetails.keywordDestinationStreet.toLowerCase())) && + (filteringDetails.keywordDestinationBuildingNumber == "" || element.destination.buildingNumber.toLowerCase().includes(filteringDetails.keywordDestinationBuildingNumber.toLowerCase())) && + + (filteringDetails.keywordDestinationApartmentNumber == "" || element.destination.apartmentNumber.toLowerCase().includes(filteringDetails.keywordDestinationApartmentNumber.toLowerCase())) && + (filteringDetails.keywordDestinationCity == "" || element.destination.city.toLowerCase().includes(filteringDetails.keywordDestinationCity.toLowerCase())) && + + (filteringDetails.keywordDestinationZipCode == "" || element.destination.zipCode.toLowerCase().includes(filteringDetails.keywordDestinationZipCode.toLowerCase())) && + (filteringDetails.keywordDestinationCountry == "" || element.destination.country.toLowerCase().includes(filteringDetails.keywordDestinationCountry.toLowerCase())) && + + // filtering of date of inquiring + (filteringDetails.minDateOfInquiring == "" || new Date(dateFromUTCToLocal(element.createdAt)) >= new Date(filteringDetails.minDateOfInquiring)) && + (filteringDetails.maxDateOfInquiring == "" || new Date(dateFromUTCToLocal(element.createdAt)) <= new Date(filteringDetails.maxDateOfInquiring)) && + + // filtering of pickup date + (filteringDetails.minPickupDate == "" || new Date(dateFromUTCToLocal(element.pickupDate)) >= new Date(filteringDetails.minPickupDate)) && + (filteringDetails.maxPickupDate == "" || new Date(dateFromUTCToLocal(element.pickupDate)) <= new Date(filteringDetails.maxPickupDate)) && + + // filtering of delivery date + (filteringDetails.minDeliveryDate == "" || new Date(dateFromUTCToLocal(element.deliveryDate)) >= new Date(filteringDetails.minDeliveryDate)) && + (filteringDetails.maxDeliveryDate == "" || new Date(dateFromUTCToLocal(element.deliveryDate)) <= new Date(filteringDetails.maxDeliveryDate)) && + + // filtering of status + (filteringDetails.filterStatus != "expired" || isPackageValid(element.validTo) == false) && + (filteringDetails.filterStatus != "valid" || isPackageValid(element.validTo) == true) && + + // filtering of additional info + (filteringDetails.filterPriority == "all" || (filteringDetails.filterPriority == element.priority)) && + (filteringDetails.filterAtWeekend == "all" || (filteringDetails.filterAtWeekend == booleanToString(element.atWeekend))) && + (filteringDetails.filterIsCompany == "all" || (filteringDetails.filterIsCompany == booleanToString(element.isCompany))) && + (filteringDetails.filterVipPackage == "all" || (filteringDetails.filterVipPackage == booleanToString(element.vipPackage))) + ); + + props.setTableData(filteredElements); + }; + + return ( + + + + +
+
+

+ Filter inquiries by attributes: +

+ {error ? ( + + {error} + + ) : null} +
+
+ +
+ +
+
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + + +
+ + + + +
+ +
+ {isLoading ? ( + + ) : ( + + )} +
+ +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/inquiries/inquiryDetailsModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/inquiries/inquiryDetailsModal.tsx new file mode 100644 index 0000000..2887bef --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/inquiries/inquiryDetailsModal.tsx @@ -0,0 +1,260 @@ +import { + Label, + Modal, + } from "flowbite-react"; + import React from "react"; + import booleanToString from "../../parsing/booleanToString"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + + interface InquiryDetailsModalProps { + show: boolean; + setShow: (show: boolean) => void; + inquiry: any; + } + + const formatDate = (date: string) => { + return `${date.substring(0, 10)}`; + }; + + const formatDateToUTC = (date: string) => { + const utcDate = dateFromUTCToLocal(date); + return `${utcDate.substring(0, 10)} ${utcDate.substring(11, 19)}`; + }; + + const LabelsWithBorder = ({ idA, valueA, idB, valueB }) => ( +
+
+ ); + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const BasicInfoDetailsSection = ({ detailsData }) => ( +
+ + + +
+ ); + + const PackageInfoDetailsSection = ({ detailsData }) => ( +
+ + + + +
+ ); + + const AddressDetailsSection = ({ prefix, detailsData }) => ( +
+ + + + + + +
+ ); + + const DateInfoDetailsSection = ({ detailsData }) => ( +
+ + + + +
+ ); + + const AdditionalInfoDetailsSection = ({ detailsData }) => ( +
+ + + + +
+ ); + + export function InquiryDetailsModal(props: InquiryDetailsModalProps) { + const close = () => { + props.setShow(false); + }; + + const submit = async (e: any) => { + e.preventDefault(); + close(); + }; + + return ( + + + + +
+
+

+ Details of inquiry: +

+
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/loginModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/loginModal.tsx index 2da8c97..ed062aa 100644 --- a/SwiftParcel.Web/frontend/src/components/modals/loginModal.tsx +++ b/SwiftParcel.Web/frontend/src/components/modals/loginModal.tsx @@ -1,4 +1,4 @@ -import { + import { Alert, Button, Label, @@ -17,6 +17,21 @@ interface LoginModalProps { setShow: (show: boolean) => void; } +const FunctionLink = ({ onClick, children, className }) => { + const handleClick = (event : any) => { + event.preventDefault(); + if (onClick) { + onClick(); + } + }; + + return ( + + {children} + + ); +}; + export function LoginModal(props: LoginModalProps) { const close = () => { setEmail(""); @@ -39,6 +54,7 @@ export function LoginModal(props: LoginModalProps) { login(email, password) .then(async (res) => { console.log(res) + saveUserInfo(res); close(); // if (res.status === 200) { @@ -116,14 +132,23 @@ export function LoginModal(props: LoginModalProps) { )}
- Not registered?{" "} - - Create account - -
+ Not registered?{" "} + + Create account + + +
+ Not want to create account?{" "} + + Continue as anonymous + +
diff --git a/SwiftParcel.Web/frontend/src/components/modals/offers/filterOfferRequestsModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/offers/filterOfferRequestsModal.tsx new file mode 100644 index 0000000..cdd8da8 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/offers/filterOfferRequestsModal.tsx @@ -0,0 +1,547 @@ +import { + Alert, + Button, + Label, + Modal, + Spinner, + TextInput, + } from "flowbite-react"; + import React from "react"; + import { HiInformationCircle } from "react-icons/hi"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + + interface FilterOfferRequestsModalProps { + show: boolean; + setShow: (show: boolean) => void; + inputData: any; + tableData: any; + setTableData: any; + pageContent: string; + } + + type FilteringDetails = { + keywordId: string; + keywordCustomerId: string; + filterStatus: string; + minOrderRequestDate: string; + maxOrderRequestDate: string; + minRequestValidTo: string; + maxRequestValidTo: string; + keywordBuyerName: string; + keywordBuyerEmail: string; + keywordBuyerAddressStreet: string; + keywordBuyerAddressBuildingNumber: string; + keywordBuyerAddressApartmentNumber: string; + keywordBuyerAddressCity: string; + keywordBuyerAddressZipCode: string; + keywordBuyerAddressCountry: string; + minDecisionDate: string; + maxDecisionDate: string; + minPickedUpAt: string; + maxPickedUpAt: string; + minDeliveredAt: string; + maxDeliveredAt: string; + minCannotDeliverAt: string; + maxCannotDeliverAt: string; + keywordCancellationReason: string; + keywordCannotDeliverReason: string; + }; + + const TextInputWithLabel = ({ id, label, value, onChange, type}) => ( +
+ + onChange(e)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md`} + /> +
+ ); + + const DateInputWithLabel = ({ id, label, value, onChange }) => { + // Function to format the date to MongoDB format + const formatToMongoDBDate = (dateString : string) => { + const date = new Date(dateString); + return date.toISOString(); + }; + + // Function to handle date change + const handleDateChange = (e : any) => { + const formattedDate = formatToMongoDBDate(e.target.value); + onChange(formattedDate); + }; + + return ( +
+ + +
+ ); + }; + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const IdFilterSection = ({ filterData, handleStringChange }) => ( +
+ + +
+ ); + + const StatusFilterSection = ({ filterData, handleStringChange, handleDateChange }) => ( +
+
+
+
+ +
+
+ + + + + + +
+ ); + + const BuyerInfoFilterSection = ({ filterData, handleStringChange }) => ( +
+ + +
+ ); + + const AddressFilterSection = ({ prefix, filterData, handleStringChange }) => ( +
+ + + + + + + + +
+ ); + + const AdditionalInfoFilterSection = ({ filterData, handleStringChange, handleDateChange }) => ( +
+ + + + + + + + + + + + + + +
+ ); + + export function FilterOfferRequestsModal(props: FilterOfferRequestsModalProps) { + const close = () => { + setError(""); + setIsLoading(false); + props.setShow(false); + }; + + const [filteringDetails, setFilteringDetails] = React.useState({ + keywordId: "", + keywordCustomerId: "", + filterStatus: "all", + minOrderRequestDate: "", + maxOrderRequestDate: "", + minRequestValidTo: "", + maxRequestValidTo: "", + keywordBuyerName: "", + keywordBuyerEmail: "", + keywordBuyerAddressStreet: "", + keywordBuyerAddressBuildingNumber: "", + keywordBuyerAddressApartmentNumber: "", + keywordBuyerAddressCity: "", + keywordBuyerAddressZipCode: "", + keywordBuyerAddressCountry: "", + minDecisionDate: "", + maxDecisionDate: "", + minPickedUpAt: "", + maxPickedUpAt: "", + minDeliveredAt: "", + maxDeliveredAt: "", + minCannotDeliverAt: "", + maxCannotDeliverAt: "", + keywordCancellationReason: "", + keywordCannotDeliverReason: "" + }); + + const handleNumberChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = parseFloat(event.target.value); + setFilteringDetails(prevState => ({ + ...prevState, + [field]: isNaN(newValue) ? 0 : newValue + })); + }; + + const handleStringChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handleDateChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const [isLoading, setIsLoading] = React.useState(false); + const [error, setError] = React.useState(""); + + const clearDetails = () => { + setFilteringDetails({ + keywordId: "", + keywordCustomerId: "", + filterStatus: "all", + minOrderRequestDate: "", + maxOrderRequestDate: "", + minRequestValidTo: "", + maxRequestValidTo: "", + keywordBuyerName: "", + keywordBuyerEmail: "", + keywordBuyerAddressStreet: "", + keywordBuyerAddressBuildingNumber: "", + keywordBuyerAddressApartmentNumber: "", + keywordBuyerAddressCity: "", + keywordBuyerAddressZipCode: "", + keywordBuyerAddressCountry: "", + minDecisionDate: "", + maxDecisionDate: "", + minPickedUpAt: "", + maxPickedUpAt: "", + minDeliveredAt: "", + maxDeliveredAt: "", + minCannotDeliverAt: "", + maxCannotDeliverAt: "", + keywordCancellationReason: "", + keywordCannotDeliverReason: "" + }); + }; + + const submit = async (e: any) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + FilterOfferRequests(); + + close(); + setIsLoading(false); + }; + + const FilterOfferRequests = () => { + const filteredElements = props.inputData.filter((element : any) => + // filtering of id section + (filteringDetails.keywordId == "" || element.id.toLowerCase().includes(filteringDetails.keywordId.toLowerCase())) && + (filteringDetails.keywordCustomerId == "" || element.customerId.toLowerCase().includes(filteringDetails.keywordCustomerId.toLowerCase())) && + + // filtering of status + (filteringDetails.filterStatus == "all" || element.status == filteringDetails.filterStatus) && + + (filteringDetails.minOrderRequestDate == "" || new Date(dateFromUTCToLocal(element.orderRequestDate)) >= new Date(filteringDetails.minOrderRequestDate)) && + (filteringDetails.maxOrderRequestDate == "" || new Date(dateFromUTCToLocal(element.orderRequestDate)) <= new Date(filteringDetails.maxOrderRequestDate)) && + + (filteringDetails.minRequestValidTo == "" || new Date(dateFromUTCToLocal(element.requestValidTo)) >= new Date(filteringDetails.minRequestValidTo)) && + (filteringDetails.maxRequestValidTo == "" || new Date(dateFromUTCToLocal(element.requestValidTo)) <= new Date(filteringDetails.maxRequestValidTo)) && + + // filtering of buyer info section + (filteringDetails.keywordBuyerName == "" || element.buyerName.toLowerCase().includes(filteringDetails.keywordBuyerName.toLowerCase())) && + (filteringDetails.keywordBuyerEmail == "" || element.buyerEmail.toLowerCase().includes(filteringDetails.keywordBuyerEmail.toLowerCase())) && + + // filtering of buyer address section + (filteringDetails.keywordBuyerAddressStreet == "" || element.buyerAddress.street.toLowerCase().includes(filteringDetails.keywordBuyerAddressStreet.toLowerCase())) && + (filteringDetails.keywordBuyerAddressBuildingNumber == "" || element.buyerAddress.buildingNumber.toLowerCase().includes(filteringDetails.keywordBuyerAddressBuildingNumber.toLowerCase())) && + + (filteringDetails.keywordBuyerAddressApartmentNumber == "" || element.buyerAddress.apartmentNumber.toLowerCase().includes(filteringDetails.keywordBuyerAddressApartmentNumber.toLowerCase())) && + (filteringDetails.keywordBuyerAddressCity == "" || element.buyerAddress.city.toLowerCase().includes(filteringDetails.keywordBuyerAddressCity.toLowerCase())) && + + (filteringDetails.keywordBuyerAddressZipCode == "" || element.buyerAddress.zipCode.toLowerCase().includes(filteringDetails.keywordBuyerAddressZipCode.toLowerCase())) && + (filteringDetails.keywordBuyerAddressCountry == "" || element.buyerAddress.country.toLowerCase().includes(filteringDetails.keywordBuyerAddressCountry.toLowerCase())) && + + // filtering of additional info + (filteringDetails.minDecisionDate == "" || new Date(dateFromUTCToLocal(element.decisionDate)) >= new Date(filteringDetails.minDecisionDate)) && + (filteringDetails.maxDecisionDate == "" || new Date(dateFromUTCToLocal(element.decisionDate)) <= new Date(filteringDetails.maxDecisionDate)) && + + (filteringDetails.minPickedUpAt == "" || new Date(dateFromUTCToLocal(element.pickedUpAt)) >= new Date(filteringDetails.minPickedUpAt)) && + (filteringDetails.maxPickedUpAt == "" || new Date(dateFromUTCToLocal(element.pickedUpAt)) <= new Date(filteringDetails.maxPickedUpAt)) && + + (filteringDetails.minDeliveredAt == "" || new Date(dateFromUTCToLocal(element.deliveredAt)) >= new Date(filteringDetails.minDeliveredAt)) && + (filteringDetails.maxDeliveredAt == "" || new Date(dateFromUTCToLocal(element.deliveredAt)) <= new Date(filteringDetails.maxDeliveredAt)) && + + (filteringDetails.minCannotDeliverAt == "" || new Date(dateFromUTCToLocal(element.cannotDeliverAt)) >= new Date(filteringDetails.minCannotDeliverAt)) && + (filteringDetails.maxCannotDeliverAt == "" || new Date(dateFromUTCToLocal(element.cannotDeliverAt)) <= new Date(filteringDetails.maxCannotDeliverAt)) && + + (filteringDetails.keywordCancellationReason == "" || element.cancellationReason.toLowerCase().includes(filteringDetails.keywordCancellationReason.toLowerCase())) && + (filteringDetails.keywordCannotDeliverReason == "" || element.cannotDeliverReason.toLowerCase().includes(filteringDetails.keywordCannotDeliverReason.toLowerCase())) + ); + + props.setTableData(filteredElements); + }; + + return ( + + + + +
+
+

+ Filter {props.pageContent == "offer-requests" ? 'offer requests' : 'pending offers'} by attributes: +

+ {error ? ( + + {error} + + ) : null} +
+
+ +
+ +
+
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ +
+ {isLoading ? ( + + ) : ( + + )} +
+ +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/offers/offerRequestDetailsModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/offers/offerRequestDetailsModal.tsx new file mode 100644 index 0000000..4de3667 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/offers/offerRequestDetailsModal.tsx @@ -0,0 +1,327 @@ +import { + Button, + Label, + Modal, + TextInput, + } from "flowbite-react"; + import React from "react"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + import formatOfferStatus from "../../parsing/formatOfferStatus"; +import { approvePendingOffer, cancelPendingOffer } from "../../../utils/api"; + + interface OfferRequestDetailsModalProps { + show: boolean; + setShow: (show: boolean) => void; + offerRequest: any; + pageContent: string; + } + + const formatDate = (date: string) => { + return `${date.substring(0, 10)} ${date.substring(11, 19)}`; + }; + + const formatDateToUTC = (date: string) => { + const utcDate = dateFromUTCToLocal(date); + return `${utcDate.substring(0, 10)} ${utcDate.substring(11, 19)}`; + }; + + const LabelsWithBorder = ({ idA, valueA, idB, valueB }) => ( +
+
+ ); + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const IdInfoDetailsSection = ({ detailsData }) => ( +
+ + +
+ ); + + const StatusDetailsSection = ({ detailsData }) => ( +
+ + + +
+ ); + + const BuyerInfoDetailsSection = ({ detailsData }) => ( +
+ + +
+ ); + + const AddressDetailsSection = ({ prefix, detailsData }) => ( +
+ + + + + + +
+ ); + + const AdditionalInfoDetailsSection = ({ detailsData }) => ( +
+ { detailsData.offerRequest.status !== "waitingfordecision" ? +
+ +
+ : null } + { detailsData.offerRequest.status === "cancelled" ? +
+ +
+ : null } + { detailsData.offerRequest.status === "pickedup" ? +
+ +
+ : null } + { detailsData.offerRequest.status === "delivered" ? +
+ +
+ : null } + { detailsData.offerRequest.status === "cannotdeliver" ? +
+ + +
+ : null } +
+ ); + + export function OfferRequestDetailsModal(props: OfferRequestDetailsModalProps) { + const close = () => { + setReason(""); + setAccepted(false); + setRejected(false); + setFinalized(false); + props.setShow(false); + }; + + const submit = async (e: any) => { + e.preventDefault(); + close(); + }; + + const [accepted, setAccepted] = React.useState(false); + const [rejected, setRejected] = React.useState(false); + const [finalized, setFinalized] = React.useState(false); + + const [reason, setReason] = React.useState(""); + + const accept = () => { + approvePendingOffer(props.offerRequest.id); + setFinalized(true); + }; + + const reject = (reason: string) => { + cancelPendingOffer(props.offerRequest.id, reason); + setFinalized(true); + }; + + const refresh = () => { + close(); + window.location.reload(); + }; + + return ( + + + + +
+
+

+ Details of {props.pageContent == "offer-requests" ? 'offer request' : 'pending offer'}: +

+
+
+ + + + + + + + + + + + + + + + + { props.pageContent == "pending-offers" ? ( +
+ + +
+ ) : null } + + { (accepted && !finalized) ? ( +
+ +
+ ) : null } + + { (rejected && !finalized) ? ( +
+ + setReason(e.target.value)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md`} + /> + +
+ ) : null } + + { finalized ? ( +
+ +
+ ) : null } + +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/offers/userDetailsModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/offers/userDetailsModal.tsx new file mode 100644 index 0000000..a25bf19 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/offers/userDetailsModal.tsx @@ -0,0 +1,440 @@ +import { + Alert, + Button, + Label, + Modal, + TextInput, + Spinner + } from "flowbite-react"; + import React from "react"; + import { HiCheckCircle } from "react-icons/hi"; + import { useNavigate } from "react-router-dom"; + import { createOrder } from "../../../utils/api"; + + interface UserDetailsModalProps { + show: boolean; + setShow: (show: boolean) => void; + userId: any; + offer: any; + userData: any; + } + + type UserInfo = { + name: string; + email: string; + addressStreet: string; + addressBuildingNumber: string; + addressApartmentNumber: string; + addressCity: string; + addressZipCode: string; + addressCountry: string; + }; + + type UserInfoErrors = { + [K in keyof UserInfo]?: string; + }; + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const TextInputWithLabel = ({ id, label, value, onChange, type, error }) => ( +
+ + onChange(e)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md ${error ? 'border-red-500' : ''}`} + /> + {error &&

{error}

} +
+ ); + + const Alerts = ({ error, success }) => ( + <> + {error && {error}} + {success && {success}} + + ); + + const SubmitButton = ({ userInfoLoading }) => ( +
+ +
+ ); + + const LabelsWithBorder = ({ idA, valueA, idB, valueB }) => ( +
+
+ ); + + const PriceBreakDownElement = ({ element }) => ( + + ); + + const BasicInfoSection = ({ userData, handleStringChange, errors }) => ( +
+ + +
+ ); + + const AddressSection = ({ userData, handleStringChange, errors }) => ( +
+ + + + + + +
+ ); + + export function UserDetailsModal(props: UserDetailsModalProps) { + const close = () => { + props.setShow(false); + }; + + const submit = async (e: any) => { + e.preventDefault(); + close(); + }; + + const userAddress = props.userData.address.split('|'); + + const [userInfo, setUserInfo] = React.useState({ + name: props.userData.fullName, + email: props.userData.email, + addressStreet: userAddress[0], + addressBuildingNumber: userAddress[1], + addressApartmentNumber: userAddress[2], + addressCity: userAddress[3], + addressZipCode: userAddress[4], + addressCountry: userAddress[5] + }); + + const handleStringChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setUserInfo(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const [formIsValid, setFormIsValid] = React.useState(true); + const [userInfoErrors, setUserInfoErrors] = React.useState({}); + + const [error, setError] = React.useState(""); + const [success, setSuccess] = React.useState(""); + + const [userInfoLoading, setUserInfoLoading] = React.useState(false); + const [requestId, setRequestId] = React.useState(""); + + const navigate = useNavigate(); + + const validateForm = () => { + const errors: UserInfoErrors = {}; + + // validation of basic info section + + if (!userInfo.name) { + errors.name = "Your name is required!"; + } + + if (!userInfo.email) { + errors.email = "Your email is required!"; + } + + // validation of address section + + if (!userInfo.addressStreet) { + errors.addressStreet = "Street in your address is required!"; + } + + if (!userInfo.addressBuildingNumber) { + errors.addressBuildingNumber = "Building number in your address is required!"; + } + + if (!userInfo.addressBuildingNumber) { + errors.addressApartmentNumber = "Building number in your address is required!"; + } + + if (!userInfo.addressCity) { + errors.addressCity = "City in your address is required!"; + } + + if (!userInfo.addressZipCode) { + errors.addressZipCode = "Zip code in your address is required!"; + } + + if (!userInfo.addressCountry) { + errors.addressCountry = "Country in your address is required!"; + } + + setUserInfoErrors(errors); + return Object.keys(errors).length === 0; + }; + + const onSubmit = (e: any) => { + e.preventDefault(); + + const isFormValid = validateForm(); + setFormIsValid(isFormValid); + + if (!isFormValid) { + return; + } + + if (userInfoLoading) return; + setError(""); + setSuccess(""); + setUserInfoLoading(true); + + createOrder( + props.userId, + props.offer.parcelId, + userInfo.name, + userInfo.email, + userInfo.addressStreet, + userInfo.addressBuildingNumber, + userInfo.addressApartmentNumber, + userInfo.addressCity, + userInfo.addressZipCode, + userInfo.addressCountry, + props.offer.company) + .then((response) => { + setRequestId(response); + setSuccess("Offer request submitted successfully!"); + setUserInfo( + { + name: "", + email: "", + addressStreet: "", + addressBuildingNumber: "", + addressApartmentNumber: "", + addressCity: "", + addressZipCode: "", + addressCountry: "" + }); + }) + + .catch((err) => { + setError(err?.response?.data?.reason || "Something went wrong!"); + }) + .finally(() => { + setUserInfoLoading(false); + }); + }; + + const redirectToHome = () => { + close(); + navigate("/", {state:{parcelId: null}}); + }; + + const redirectToOrders = () => { + close(); + navigate("/orders", {state:{parcelId: null}}); + }; + + return ( + + + + +
+
+
+ {!success ? ( +
+

+ Check price details and complete data if necessary: +

+ +
+
+
+ +
+ + + + {props.offer.priceBreakDown != null && props.offer.priceBreakDown?.length > 0 ? ( + props.offer.priceBreakDown?.map((element: any) => element != null ? ( + + ) : null) + ) : null} + + + + + + + + + + {!formIsValid &&

Please fill in all required fields.

} + + + + {error ? null : } +
+ +
+
+
+
+ ) : null} + {success ? ( +
+

+ Offer request submitted successfully! +

+ +
+
+
+ + + + Success! {success} + + + +
+
+
+ + + +
+
+
+ +

Remember to confirm or cancel your offer request after potential approval.

+ + { (props.userId === null) ? +
+

You can do it by going to home page and typing the id presented above in "Order id" field.

+
+ : null } + +
+
+
+ + { (props.userId === null) ? +
+
+ +
+
+ : +
+
+ +
+
+ } +
+ ) : null} +
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/orders/addOrderByIdModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/orders/addOrderByIdModal.tsx new file mode 100644 index 0000000..45522e3 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/orders/addOrderByIdModal.tsx @@ -0,0 +1,115 @@ +import { + Alert, + Button, + Label, + Modal, + Spinner, + TextInput, + } from "flowbite-react"; + import React from "react"; + import { BsBoxSeam } from "react-icons/bs"; + import { HiInformationCircle } from "react-icons/hi"; + import { addCustomerToOrder } from "../../../utils/api"; + import { getUserIdFromStorage, getUserInfo } from "../../../utils/storage"; + + interface AddOrderByIdModalProps { + show: boolean; + setShow: (show: boolean) => void; + } + + export function AddOrderByIdModal(props: AddOrderByIdModalProps) { + const close = () => { + setOrderId(""); + setIsLoading(false); + setError(""); + props.setShow(false); + + if (success !== "") { + setSuccess(""); + window.location.reload(); + } + }; + + const [orderId, setOrderId] = React.useState(""); + const [isLoading, setIsLoading] = React.useState(false); + const [error, setError] = React.useState(""); + const [success, setSuccess] = React.useState(""); + + const submit = async (e: any) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + addCustomerToOrder(orderId, getUserIdFromStorage()) + .then((res) => { + setSuccess("Order added successfully!"); + }) + .catch((err) => { + setError(err?.response?.data?.reason || "Something went wrong!"); + }) + .finally(() => { + setIsLoading(false); + }); + }; + + return ( + + + + +
+
+

+ Add order by id: +

+
+ {error ? ( + + {error} + + ) : null} + {success ? ( + + + Success! {success} + + + ) : null} +
+
+ setOrderId(e.target.value)} + /> + +
+ +
+ {isLoading ? ( + + ) : ( + + )} +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/orders/filterOrdersModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/orders/filterOrdersModal.tsx new file mode 100644 index 0000000..80a7a1f --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/orders/filterOrdersModal.tsx @@ -0,0 +1,547 @@ +import { + Alert, + Button, + Label, + Modal, + Spinner, + TextInput, + } from "flowbite-react"; + import React from "react"; + import { HiInformationCircle } from "react-icons/hi"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + + interface FilterOrdersModalProps { + show: boolean; + setShow: (show: boolean) => void; + inputData: any; + tableData: any; + setTableData: any; + } + + type FilteringDetails = { + keywordId: string; + keywordInquiryId: string; + keywordCourierCompany: string; + minOrderRequestDate: string; + maxOrderRequestDate: string; + minRequestValidTo: string; + maxRequestValidTo: string; + minDecisionDate: string; + maxDecisionDate: string; + filterStatus: string; + minPickedUpAt: string; + maxPickedUpAt: string; + minDeliveredAt: string; + maxDeliveredAt: string; + minCannotDeliverAt: string; + maxCannotDeliverAt: string; + keywordCancellationReason: string; + keywordCannotDeliverReason: string; + keywordBuyerName: string; + keywordBuyerEmail: string; + keywordBuyerAddressStreet: string; + keywordBuyerAddressBuildingNumber: string; + keywordBuyerAddressApartmentNumber: string; + keywordBuyerAddressCity: string; + keywordBuyerAddressZipCode: string; + keywordBuyerAddressCountry: string; + }; + + const TextInputWithLabel = ({ id, label, value, onChange, type}) => ( +
+ + onChange(e)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md`} + /> +
+ ); + + const DateInputWithLabel = ({ id, label, value, onChange }) => { + // Function to format the date to MongoDB format + const formatToMongoDBDate = (dateString : string) => { + const date = new Date(dateString); + return date.toISOString(); + }; + + // Function to handle date change + const handleDateChange = (e : any) => { + const formattedDate = formatToMongoDBDate(e.target.value); + onChange(formattedDate); + }; + + return ( +
+ + +
+ ); + }; + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const BasicInfoFilterSection = ({ filterData, handleStringChange }) => ( +
+ + + +
+ ); + + const DateInfoFilterSection = ({ filterData, handleDateChange }) => ( +
+ + + + + + + + +
+ ); + + const StatusInfoFilterSection = ({ filterData, handleStringChange, handleDateChange }) => ( +
+
+
+
+ +
+
+ + + + + + + + + + + + +
+ ); + + const BuyerInfoFilterSection = ({ filterData, handleStringChange }) => ( +
+ + +
+ ); + + const AddressFilterSection = ({ prefix, filterData, handleStringChange }) => ( +
+ + + + + + + + +
+ ); + + export function FilterOrdersModal(props: FilterOrdersModalProps) { + const close = () => { + setError(""); + setIsLoading(false); + props.setShow(false); + }; + + const [filteringDetails, setFilteringDetails] = React.useState({ + keywordId: "", + keywordInquiryId: "", + keywordCourierCompany: "", + minOrderRequestDate: "", + maxOrderRequestDate: "", + minRequestValidTo: "", + maxRequestValidTo: "", + minDecisionDate: "", + maxDecisionDate: "", + filterStatus: "all", + minPickedUpAt: "", + maxPickedUpAt: "", + minDeliveredAt: "", + maxDeliveredAt: "", + minCannotDeliverAt: "", + maxCannotDeliverAt: "", + keywordCancellationReason: "", + keywordCannotDeliverReason: "", + keywordBuyerName: "", + keywordBuyerEmail: "", + keywordBuyerAddressStreet: "", + keywordBuyerAddressBuildingNumber: "", + keywordBuyerAddressApartmentNumber: "", + keywordBuyerAddressCity: "", + keywordBuyerAddressZipCode: "", + keywordBuyerAddressCountry: "" + }); + + const handleStringChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handleDateChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event; + setFilteringDetails(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const [isLoading, setIsLoading] = React.useState(false); + const [error, setError] = React.useState(""); + + const clearDetails = () => { + setFilteringDetails({ + keywordId: "", + keywordInquiryId: "", + keywordCourierCompany: "", + minOrderRequestDate: "", + maxOrderRequestDate: "", + minRequestValidTo: "", + maxRequestValidTo: "", + minDecisionDate: "", + maxDecisionDate: "", + filterStatus: "all", + minPickedUpAt: "", + maxPickedUpAt: "", + minDeliveredAt: "", + maxDeliveredAt: "", + minCannotDeliverAt: "", + maxCannotDeliverAt: "", + keywordCancellationReason: "", + keywordCannotDeliverReason: "", + keywordBuyerName: "", + keywordBuyerEmail: "", + keywordBuyerAddressStreet: "", + keywordBuyerAddressBuildingNumber: "", + keywordBuyerAddressApartmentNumber: "", + keywordBuyerAddressCity: "", + keywordBuyerAddressZipCode: "", + keywordBuyerAddressCountry: "" + }); + }; + + const submit = async (e: any) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + FilterOrders(); + + close(); + setIsLoading(false); + }; + + const FilterOrders = () => { + const filteredElements = props.inputData.filter((element : any) => + // filtering of id section + (filteringDetails.keywordId == "" || element.id.toLowerCase().includes(filteringDetails.keywordId.toLowerCase())) && + (filteringDetails.keywordInquiryId == "" || element.parcel.id.toLowerCase().includes(filteringDetails.keywordInquiryId.toLowerCase())) && + (filteringDetails.keywordCourierCompany == "" || element.courierCompany.toLowerCase().includes(filteringDetails.keywordCourierCompany.toLowerCase())) && + + (filteringDetails.minOrderRequestDate == "" || new Date(dateFromUTCToLocal(element.orderRequestDate)) >= new Date(filteringDetails.minOrderRequestDate)) && + (filteringDetails.maxOrderRequestDate == "" || new Date(dateFromUTCToLocal(element.orderRequestDate)) <= new Date(filteringDetails.maxOrderRequestDate)) && + + (filteringDetails.minRequestValidTo == "" || new Date(dateFromUTCToLocal(element.requestValidTo)) >= new Date(filteringDetails.minRequestValidTo)) && + (filteringDetails.maxRequestValidTo == "" || new Date(dateFromUTCToLocal(element.requestValidTo)) <= new Date(filteringDetails.maxRequestValidTo)) && + + (filteringDetails.minDecisionDate == "" || new Date(dateFromUTCToLocal(element.decisionDate)) >= new Date(filteringDetails.minDecisionDate)) && + (filteringDetails.maxDecisionDate == "" || new Date(dateFromUTCToLocal(element.decisionDate)) <= new Date(filteringDetails.maxDecisionDate)) && + + // filtering of status + (filteringDetails.filterStatus == "all" || element.status == filteringDetails.filterStatus) && + + (filteringDetails.minPickedUpAt == "" || new Date(dateFromUTCToLocal(element.pickedUpAt)) >= new Date(filteringDetails.minPickedUpAt)) && + (filteringDetails.maxPickedUpAt == "" || new Date(dateFromUTCToLocal(element.pickedUpAt)) <= new Date(filteringDetails.maxPickedUpAt)) && + + (filteringDetails.minDeliveredAt == "" || new Date(dateFromUTCToLocal(element.deliveredAt)) >= new Date(filteringDetails.minDeliveredAt)) && + (filteringDetails.maxDeliveredAt == "" || new Date(dateFromUTCToLocal(element.deliveredAt)) <= new Date(filteringDetails.maxDeliveredAt)) && + + (filteringDetails.minCannotDeliverAt == "" || new Date(dateFromUTCToLocal(element.cannotDeliverAt)) >= new Date(filteringDetails.minCannotDeliverAt)) && + (filteringDetails.maxCannotDeliverAt == "" || new Date(dateFromUTCToLocal(element.cannotDeliverAt)) <= new Date(filteringDetails.maxCannotDeliverAt)) && + + (filteringDetails.keywordCancellationReason == "" || element.cancellationReason.toLowerCase().includes(filteringDetails.keywordCancellationReason.toLowerCase())) && + (filteringDetails.keywordCannotDeliverReason == "" || element.cannotDeliverReason.toLowerCase().includes(filteringDetails.keywordCannotDeliverReason.toLowerCase())) && + + // filtering of buyer info section + (filteringDetails.keywordBuyerName == "" || element.buyerName.toLowerCase().includes(filteringDetails.keywordBuyerName.toLowerCase())) && + (filteringDetails.keywordBuyerEmail == "" || element.buyerEmail.toLowerCase().includes(filteringDetails.keywordBuyerEmail.toLowerCase())) && + + // filtering of buyer address section + (filteringDetails.keywordBuyerAddressStreet == "" || element.buyerAddress.street.toLowerCase().includes(filteringDetails.keywordBuyerAddressStreet.toLowerCase())) && + (filteringDetails.keywordBuyerAddressBuildingNumber == "" || element.buyerAddress.buildingNumber.toLowerCase().includes(filteringDetails.keywordBuyerAddressBuildingNumber.toLowerCase())) && + + (filteringDetails.keywordBuyerAddressApartmentNumber == "" || element.buyerAddress.apartmentNumber.toLowerCase().includes(filteringDetails.keywordBuyerAddressApartmentNumber.toLowerCase())) && + (filteringDetails.keywordBuyerAddressCity == "" || element.buyerAddress.city.toLowerCase().includes(filteringDetails.keywordBuyerAddressCity.toLowerCase())) && + + (filteringDetails.keywordBuyerAddressZipCode == "" || element.buyerAddress.zipCode.toLowerCase().includes(filteringDetails.keywordBuyerAddressZipCode.toLowerCase())) && + (filteringDetails.keywordBuyerAddressCountry == "" || element.buyerAddress.country.toLowerCase().includes(filteringDetails.keywordBuyerAddressCountry.toLowerCase())) + ); + + props.setTableData(filteredElements); + }; + + return ( + + + + +
+
+

+ Filter orders by attributes: +

+ {error ? ( + + {error} + + ) : null} +
+
+ +
+ +
+
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ +
+ {isLoading ? ( + + ) : ( + + )} +
+ +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/modals/orders/orderDetailsModal.tsx b/SwiftParcel.Web/frontend/src/components/modals/orders/orderDetailsModal.tsx new file mode 100644 index 0000000..632d2ab --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/modals/orders/orderDetailsModal.tsx @@ -0,0 +1,296 @@ +import { + Button, + Label, + Modal, + TextInput, + } from "flowbite-react"; + import React from "react"; + import dateFromUTCToLocal from "../../parsing/dateFromUTCToLocal"; + import formatOfferStatus from "../../parsing/formatOfferStatus"; +import { confirmOrder, cancelOrder } from "../../../utils/api"; + + interface OrderDetailsModalProps { + show: boolean; + setShow: (show: boolean) => void; + order: any; + } + + const formatDate = (date: string) => { + return `${date.substring(0, 10)} ${date.substring(11, 19)}`; + }; + + const formatDateToUTC = (date: string) => { + const utcDate = dateFromUTCToLocal(date); + return `${utcDate.substring(0, 10)} ${utcDate.substring(11, 19)}`; + }; + + const LabelsWithBorder = ({ idA, valueA, idB, valueB }) => ( +
+
+ ); + + const SectionTitle = ({ title }) => ( +
+

{title}

+
+ ); + + const BasicInfoDetailsSection = ({ detailsData }) => ( +
+ + + + { detailsData.order.status === "waitingfordecision" ? + + : + + } +
+ ); + + const StatusDetailsSection = ({ detailsData, confirm, cancel, finalized, refresh }) => ( +
+ + { detailsData.order.status === "approved" ? +
+ { (new Date() < new Date(detailsData.order.requestValidTo)) ? ( +
+ + +
+ ) : ( +
+
+ ) } + + { finalized ? ( +
+ +
+ ) : null } +
+ : null } + { detailsData.order.status === "cancelled" ? + + : null } + { detailsData.order.status === "pickedup" ? + + : null } + { detailsData.order.status === "delivered" ? + + : null } + { detailsData.order.status === "cannotdeliver" ? +
+ + +
+ : null } +
+ ); + + const BuyerInfoDetailsSection = ({ detailsData }) => ( +
+ + +
+ ); + + const AddressDetailsSection = ({ prefix, detailsData }) => ( +
+ + + + + + +
+ ); + + export function OrderDetailsModal(props: OrderDetailsModalProps) { + const close = () => { + setReason(""); + setFinalized(false); + props.setShow(false); + }; + + const submit = async (e: any) => { + e.preventDefault(); + close(); + }; + + const [finalized, setFinalized] = React.useState(false); + + const [reason, setReason] = React.useState(""); + + const confirm = () => { + confirmOrder(props.order.id, props.order.company); + setFinalized(true); + }; + + const cancel = () => { + cancelOrder(props.order.id, props.order.company); + setFinalized(true); + }; + + const refresh = () => { + close(); + window.location.reload(); + }; + + return ( + + + + +
+
+

+ Details of your order: +

+
+
+ + + + + + + + + + + + + +
+ +
+
+
+
+
+
+
+ ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/parsing/dateFromUTCToLocal.tsx b/SwiftParcel.Web/frontend/src/components/parsing/dateFromUTCToLocal.tsx new file mode 100644 index 0000000..226fc2f --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/parsing/dateFromUTCToLocal.tsx @@ -0,0 +1,7 @@ +export function dateFromUTCToLocal(utcString : string) { + const utcDate = new Date(utcString); + const localDate = new Date(utcDate.getTime() + 60 * 60 * 1000); + return localDate.toISOString(); +}; + +export default dateFromUTCToLocal; \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/parsing/formatDeliveryStatus.tsx b/SwiftParcel.Web/frontend/src/components/parsing/formatDeliveryStatus.tsx new file mode 100644 index 0000000..a42fab2 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/parsing/formatDeliveryStatus.tsx @@ -0,0 +1,12 @@ +const formatDeliveryStatus = (status: string) => { + switch (status) { + case 'inprogress': + return "in progress"; + case 'cannotdeliver': + return "cannot deliver"; + default: + return status; + } +}; + +export default formatDeliveryStatus; \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/components/parsing/formatOfferStatus.tsx b/SwiftParcel.Web/frontend/src/components/parsing/formatOfferStatus.tsx new file mode 100644 index 0000000..5e493ae --- /dev/null +++ b/SwiftParcel.Web/frontend/src/components/parsing/formatOfferStatus.tsx @@ -0,0 +1,14 @@ +const formatOfferStatus = (status: string) => { + switch (status) { + case 'waitingfordecision': + return "waiting for decision"; + case 'pickedup': + return "picked up"; + case 'cannotdeliver': + return "cannot deliver"; + default: + return status; + } +}; + +export default formatOfferStatus; \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/couriers/courierOffers.tsx b/SwiftParcel.Web/frontend/src/pages/couriers/courierOffers.tsx new file mode 100644 index 0000000..a6f9212 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/couriers/courierOffers.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + Alert, + Button, + Label, + TextInput, + Spinner, +} from "flowbite-react"; +// ... other imports + +export const CourierOffers = ({ offers, onSelectOffer }) => { + if (!offers || offers.length === 0) { + return

No offers available at this moment.

; + } + + return ( +
+

Available Courier Offers

+
+ {offers.map(offer => ( +
+
+
+

{offer.courierName}

+

Price: ${offer.price}

+
+ +
+
+ ))} +
+
+ ); +}; diff --git a/SwiftParcel.Web/frontend/src/pages/deliveries.tsx b/SwiftParcel.Web/frontend/src/pages/deliveries.tsx index 9a01af3..4033c38 100644 --- a/SwiftParcel.Web/frontend/src/pages/deliveries.tsx +++ b/SwiftParcel.Web/frontend/src/pages/deliveries.tsx @@ -4,7 +4,7 @@ import { Footer } from "../components/footer"; import { Header } from "../components/header"; import { Loader } from "../components/loader"; // import Stats from "../components/stats"; -import { getCoordinates, getParcel, updateParcel } from "../utils/api"; +import {getParcel, updateParcel } from "../utils/api"; import GoogleMapReact from "google-map-react"; import { IoLocationSharp } from "react-icons/io5"; @@ -41,20 +41,20 @@ export default function Deliveries() { } }, []); - React.useEffect(() => { - if (parcel) { - getCoordinates(parcel?.receiverAddress).then((res) => { - if (res?.status === 200 && res?.data?.length > 0) { - const { lat, lon, display_name } = res?.data[0]; - setLocation({ - center: { lat: parseFloat(lat), lng: parseFloat(lon) }, - name: display_name, - sName: parcel?.receiverAddress, - }); - } - }); - } - }, [parcel]); + // React.useEffect(() => { + // if (parcel) { + // getCoordinates(parcel?.receiverAddress).then((res) => { + // if (res?.status === 200 && res?.data?.length > 0) { + // const { lat, lon, display_name } = res?.data[0]; + // setLocation({ + // center: { lat: parseFloat(lat), lng: parseFloat(lon) }, + // name: display_name, + // sName: parcel?.receiverAddress, + // }); + // } + // }); + // } + // }, [parcel]); const LocationPin = ({ text }: any) => (
diff --git a/SwiftParcel.Web/frontend/src/pages/deliveries/deliveriesCourier.tsx b/SwiftParcel.Web/frontend/src/pages/deliveries/deliveriesCourier.tsx new file mode 100644 index 0000000..c7d2396 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/deliveries/deliveriesCourier.tsx @@ -0,0 +1,273 @@ +import { Table, Pagination, Button } from "flowbite-react"; +import React from "react"; +import { Header } from "../../components/header"; +import { Footer } from "../../components/footer"; +import { getYourDeliveries, getPendingDeliveries } from "../../utils/api"; +import { Loader } from "../../components/loader"; +import { DeliveryDetails } from "../../components/details/delivery"; +import { FilterDeliveriesModal } from "../../components/modals/deliveries/filterDeliveriesModal"; +import { getUserIdFromStorage } from "../../utils/storage"; +import booleanToString from "../../components/parsing/booleanToString"; + +export default function DeliveriesCourier(pageContent: string) { + const [page, setPage] = React.useState(1); + const [inputData, setInputData] = React.useState(null); + const [tableData, setTableData] = React.useState(null); + + const [sortedColumn, setSortedColumn] = React.useState(null); + const [sortDirection, setSortDirection] = React.useState('ascending'); + + const [showFilterDeliveriesModal, setShowFilterDeliveriesModal] = React.useState(false); + + const [loadingHeader, setLoadingHeader] = React.useState(true); + const [loadingDeliveries, setLoadingDeliveries] = React.useState(true); + + React.useEffect(() => { + + ((pageContent == "your-deliveries") ? getYourDeliveries(getUserIdFromStorage()) : getPendingDeliveries()) + .then((res) => { + if (res.status === 200) { + setInputData(res?.data); + setTableData(res?.data); + } else { + throw new Error(); + } + }) + .catch((err) => { + setInputData(null); + setTableData(null); + }) + .finally(() => { + setLoadingDeliveries(false); + }); + + }, [page]); + + const onPageChange = (page: number) => { + setPage(page); + }; + + const handleSort = (column : string) => { + let direction = 'ascending'; + if (sortedColumn === column && sortDirection === 'ascending') { + direction = 'descending'; + } + + const sortedData = [...tableData].sort((a, b) => { + switch (column) { + case 'id': { + const idA = a.id; + const idB = b.id; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'sourceAddress': { + const addressA = `${a.source.street} ${a.source.buildingNumber} ${a.source.apartmentNumber} + ${a.source.zipCode} ${a.source.city} ${a.source.country}`; + const addressB = `${b.source.street} ${b.source.buildingNumber} ${b.source.apartmentNumber} + ${b.source.zipCode} ${b.source.city} ${b.source.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + case 'destinationAddress': { + const addressA = `${a.destination.street} ${a.destination.buildingNumber} ${a.destination.apartmentNumber} + ${a.destination.zipCode} ${a.destination.city} ${a.destination.country}`; + const addressB = `${b.destination.street} ${b.destination.buildingNumber} ${b.destination.apartmentNumber} + ${b.destination.zipCode} ${b.destination.city} ${b.destination.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + case 'pickupDate': { + const dateA = new Date(a.pickupDate); + const dateB = new Date(b.pickupDate); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'deliveryDate': { + const dateA = new Date(a.deliveryDate); + const dateB = new Date(b.deliveryDate); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'priority': { + const idA = a.priority; + const idB = b.priority; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'atWeekend': { + const idA = booleanToString(a.atWeekend); + const idB = booleanToString(b.atWeekend); + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'status': { + const idA = a.status; + const idB = b.status; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'lastUpdate': { + const dateA = new Date(a.lastUpdate); + const dateB = new Date(b.lastUpdate); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + default: + return 0; + } + }); + + setSortedColumn(column); + setSortDirection(direction); + setTableData(sortedData); + }; + + const getSortIcon = (column : string) => { + return sortedColumn === column ? (sortDirection === 'ascending' ? '▲' : '▼') : ''; + } + + const tableHeaderStyle = { + row: { + display: 'flex', + justifyContent: 'space-between', + }, + left: { + alignSelf: 'flex-start', + }, + right: { + alignSelf: 'flex-end', + }, + }; + + return ( + <> + {loadingHeader || loadingDeliveries ? : null} +
+
+
+

+ {pageContent == "your-deliveries" ? 'Your deliveries' : 'Pending deliveries'} +

+ + +
+ { pageContent == "your-deliveries" ? ( +

+ To set a delivery as delivered or cannot deliver, open details by clicking button in the last column. +

+ ) : +

+ To set a delivery as picked up, open details by clicking button in the last column. +

+ } + + + + handleSort('id')}> + Id {getSortIcon('id')} + + handleSort('sourceAddress')}> + Source address {getSortIcon('sourceAddress')} + + handleSort('destinationAddress')}> + Destination address {getSortIcon('destinationAddress')} + + handleSort('pickupDate')}> + Pickup date {getSortIcon('pickupDate')} + + handleSort('deliveryDate')}> + Delivery date {getSortIcon('deliveryDate')} + + handleSort('priority')}> + Priority {getSortIcon('priority')} + + handleSort('atWeekend')}> + At weekend {getSortIcon('atWeekend')} + + handleSort('status')}> + Status {getSortIcon('status')} + + handleSort('lastUpdate')}> + Last update {getSortIcon('lastUpdate')} + + + Details + + + + {tableData != null && tableData?.length > 0 ? ( + tableData?.map((delivery: any) => ( + + )) + ) : ( + + + + )} + +
+ No {pageContent == "your-deliveries" ? 'your deliveries' : 'pending deliveries'} found +
+ {tableData != null && tableData?.total_pages > 1 ? ( + + ) : null} +
+
+ + ); +} diff --git a/SwiftParcel.Web/frontend/src/pages/deliveries/pendingDeliveries.tsx b/SwiftParcel.Web/frontend/src/pages/deliveries/pendingDeliveries.tsx new file mode 100644 index 0000000..ac27f04 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/deliveries/pendingDeliveries.tsx @@ -0,0 +1,5 @@ +import DeliveriesCourier from "./deliveriesCourier"; + +export default function PendingDeliveries() { + return DeliveriesCourier("pending-deliveries"); +} \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/deliveries/yourDeliveries.tsx b/SwiftParcel.Web/frontend/src/pages/deliveries/yourDeliveries.tsx new file mode 100644 index 0000000..8a52c07 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/deliveries/yourDeliveries.tsx @@ -0,0 +1,5 @@ +import DeliveriesCourier from "./deliveriesCourier"; + +export default function YourDeliveries() { + return DeliveriesCourier("your-deliveries"); +} \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/dist/createInquiry.js b/SwiftParcel.Web/frontend/src/pages/dist/createInquiry.js new file mode 100644 index 0000000..d985d5d --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/dist/createInquiry.js @@ -0,0 +1,181 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +exports.__esModule = true; +var flowbite_react_1 = require("flowbite-react"); +var react_1 = require("react"); +var hi_1 = require("react-icons/hi"); +var footer_1 = require("../components/footer"); +var header_1 = require("../components/header"); +var loader_1 = require("../components/loader"); +var api_1 = require("../utils/api"); +var TextInputWithLabel = function (_a) { + var id = _a.id, label = _a.label, value = _a.value, onChange = _a.onChange; + return (react_1["default"].createElement("div", { className: "mb-4 flex-col flex" }, + react_1["default"].createElement(flowbite_react_1.Label, { htmlFor: id, className: "mb-2 block text-sm font-medium text-gray-700 " }, label), + react_1["default"].createElement(flowbite_react_1.TextInput, { id: id, type: "text", value: value, onChange: function (e) { return onChange(e.target.value); }, className: "border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md" }))); +}; +var DateInputWithLabel = function (_a) { + var id = _a.id, label = _a.label, value = _a.value, onChange = _a.onChange; + return (react_1["default"].createElement("div", { className: "mb-4" }, + react_1["default"].createElement(flowbite_react_1.Label, { htmlFor: id }, label), + react_1["default"].createElement(flowbite_react_1.TextInput, { id: id, type: "date", value: value, onChange: function (e) { return onChange(e.target.value); } }))); +}; +var SectionTitle = function (_a) { + var title = _a.title; + return (react_1["default"].createElement("div", { className: "mb-4 border-b border-gray-300 pb-1" }, + react_1["default"].createElement("h2", { className: "text-xl font-semibold text-gray-800" }, title))); +}; +var Alerts = function (_a) { + var error = _a.error, success = _a.success; + return (react_1["default"].createElement(react_1["default"].Fragment, null, + error && react_1["default"].createElement(flowbite_react_1.Alert, { color: "failure" }, error), + success && react_1["default"].createElement(flowbite_react_1.Alert, { color: "success" }, success))); +}; +var SubmitButton = function (_a) { + var inquiryLoading = _a.inquiryLoading; + return (react_1["default"].createElement("div", { className: "flex justify-end" }, + react_1["default"].createElement(flowbite_react_1.Button, { type: "submit", disabled: inquiryLoading }, inquiryLoading ? react_1["default"].createElement(flowbite_react_1.Spinner, null) : "Create new inquiry"))); +}; +var ShortDescriptionSection = function (_a) { + var description = _a.description, setDescription = _a.setDescription; + return (react_1["default"].createElement("div", { className: "my-5" }, + react_1["default"].createElement(flowbite_react_1.Label, { htmlFor: "description" }, "Short Description:"), + react_1["default"].createElement(flowbite_react_1.TextInput, { id: "description", value: description, onChange: function (e) { return setDescription(e.target.value); } }))); +}; +var PackageDetailsSection = function (_a) { + var packageWidth = _a.packageWidth, setPackageWidth = _a.setPackageWidth, packageHeight = _a.packageHeight, setPackageHeight = _a.setPackageHeight, packageDepth = _a.packageDepth, setPackageDepth = _a.setPackageDepth, packageWeight = _a.packageWeight, setPackageWeight = _a.setPackageWeight; + return (react_1["default"].createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, + react_1["default"].createElement(TextInputWithLabel, { id: "package-width", label: "Width", value: packageWidth, onChange: setPackageWidth }), + react_1["default"].createElement(TextInputWithLabel, { id: "package-height", label: "Height", value: packageHeight, onChange: setPackageHeight }), + react_1["default"].createElement(TextInputWithLabel, { id: "package-depth", label: "Depth", value: packageDepth, onChange: setPackageDepth }), + react_1["default"].createElement(TextInputWithLabel, { id: "package-weight", label: "Weight", value: packageWeight, onChange: setPackageWeight }))); +}; +var DeliveryDetailsSection = function (_a) { + var pickupDate = _a.pickupDate, setPickupDate = _a.setPickupDate, deliveryDate = _a.deliveryDate, setDeliveryDate = _a.setDeliveryDate, priority = _a.priority, setPriority = _a.setPriority, atWeekend = _a.atWeekend, setAtWeekend = _a.setAtWeekend, isCompany = _a.isCompany, setIsCompany = _a.setIsCompany, vipPackage = _a.vipPackage, setVipPackage = _a.setVipPackage; + return (react_1["default"].createElement("div", { className: "grid grid-cols-2 gap-4" }, + react_1["default"].createElement(DateInputWithLabel, { id: "pickup-date", label: "Pickup Date", value: pickupDate, onChange: setPickupDate }), + react_1["default"].createElement(DateInputWithLabel, { id: "delivery-date", label: "Delivery Date", value: deliveryDate, onChange: setDeliveryDate }))); +}; +var AddressSection = function (_a) { + var prefix = _a.prefix, street = _a.street, setStreet = _a.setStreet, buildingNumber = _a.buildingNumber, setBuildingNumber = _a.setBuildingNumber, apartmentNumber = _a.apartmentNumber, setApartmentNumber = _a.setApartmentNumber, city = _a.city, setCity = _a.setCity, zipCode = _a.zipCode, setZipCode = _a.setZipCode, country = _a.country, setCountry = _a.setCountry; + return (react_1["default"].createElement("div", { className: "grid grid-cols-2 gap-4" }, + react_1["default"].createElement(TextInputWithLabel, { id: prefix + "-address-street", label: "Street", value: street, onChange: function (e) { return setStreet(e.target.value); } }), + react_1["default"].createElement(TextInputWithLabel, { id: prefix + "-address-building-number", label: "Building Number", value: buildingNumber, onChange: function (e) { return setBuildingNumber(e.target.value); } }), + react_1["default"].createElement(TextInputWithLabel, { id: prefix + "-address-apartment-number", label: "Apartment Number (optional)", value: apartmentNumber, onChange: function (e) { return setApartmentNumber(e.target.value); } }), + react_1["default"].createElement(TextInputWithLabel, { id: prefix + "-address-city", label: "City", value: city, onChange: function (e) { return setCity(e.target.value); } }), + react_1["default"].createElement(TextInputWithLabel, { id: prefix + "-address-zip-code", label: "Zip Code", value: zipCode, onChange: function (e) { return setZipCode(e.target.value); } }), + react_1["default"].createElement(TextInputWithLabel, { id: prefix + "-address-country", label: "Country", value: country, onChange: function (e) { return setCountry(e.target.value); } }))); +}; +function CreateInquiry() { + var _a = react_1["default"].useState(true), loading = _a[0], setLoading = _a[1]; + var _b = react_1["default"].useState(true), formIsValid = _b[0], setFormIsValid = _b[1]; + var _c = react_1["default"].useState(""), description = _c[0], setDescription = _c[1]; + var _d = react_1["default"].useState(0), packageWidth = _d[0], setPackageWidth = _d[1]; + var _e = react_1["default"].useState(0), packageHeight = _e[0], setPackageHeight = _e[1]; + var _f = react_1["default"].useState(0), packageDepth = _f[0], setPackageDepth = _f[1]; + var _g = react_1["default"].useState(0), packageWeight = _g[0], setPackageWeight = _g[1]; + var _h = react_1["default"].useState(""), sourceAddressStreet = _h[0], setSourceAddressStreet = _h[1]; + var _j = react_1["default"].useState(""), sourceAddressBuildingNumber = _j[0], setSourceAddressBuildingNumber = _j[1]; + var _k = react_1["default"].useState(""), sourceAddressApartmentNumber = _k[0], setSourceAddressApartmentNumber = _k[1]; + var _l = react_1["default"].useState(""), sourceAddressCity = _l[0], setSourceAddressCity = _l[1]; + var _m = react_1["default"].useState(""), sourceAddressZipCode = _m[0], setSourceAddressZipCode = _m[1]; + var _o = react_1["default"].useState(""), sourceAddressCountry = _o[0], setSourceAddressCountry = _o[1]; + var _p = react_1["default"].useState(""), destinationAddressStreet = _p[0], setDestinationAddressStreet = _p[1]; + var _q = react_1["default"].useState(""), destinationAddressBuildingNumber = _q[0], setDestinationAddressBuildingNumber = _q[1]; + var _r = react_1["default"].useState(""), destinationAddressApartmentNumber = _r[0], setDestinationAddressApartmentNumber = _r[1]; + var _s = react_1["default"].useState(""), destinationAddressCity = _s[0], setDestinationAddressCity = _s[1]; + var _t = react_1["default"].useState(""), destinationAddressZipCode = _t[0], setDestinationAddressZipCode = _t[1]; + var _u = react_1["default"].useState(""), destinationAddressCountry = _u[0], setDestinationAddressCountry = _u[1]; + var _v = react_1["default"].useState(""), pickupDate = _v[0], setPickupDate = _v[1]; + var _w = react_1["default"].useState(""), deliveryDate = _w[0], setDeliveryDate = _w[1]; + var _x = react_1["default"].useState("low"), priority = _x[0], setPriority = _x[1]; + var _y = react_1["default"].useState(false), atWeekend = _y[0], setAtWeekend = _y[1]; + var _z = react_1["default"].useState(false), isCompany = _z[0], setIsCompany = _z[1]; + var _0 = react_1["default"].useState(false), vipPackage = _0[0], setVipPackage = _0[1]; + var _1 = react_1["default"].useState(""), error = _1[0], setError = _1[1]; + var _2 = react_1["default"].useState(""), success = _2[0], setSuccess = _2[1]; + var _3 = react_1["default"].useState(false), inquiryLoading = _3[0], setInquiryLoading = _3[1]; + var validateForm = function () { + // Example validation logic + return description !== '' && packageWidth > 0 && packageHeight > 0 && packageDepth > 0 && packageWeight > 0; + }; + var onSubmit = function (e) { + e.preventDefault(); + var isFormValid = validateForm(); + setFormIsValid(isFormValid); + if (inquiryLoading) + return; + setError(""); + setSuccess(""); + setInquiryLoading(true); + api_1.createInquiry(description, packageWidth, packageHeight, packageDepth, packageWeight, sourceAddressStreet, sourceAddressBuildingNumber, sourceAddressApartmentNumber, sourceAddressCity, sourceAddressZipCode, sourceAddressCountry, destinationAddressStreet, destinationAddressBuildingNumber, destinationAddressApartmentNumber, destinationAddressCity, destinationAddressZipCode, destinationAddressCountry, priority, atWeekend, pickupDate + "T00:00:00.000Z", deliveryDate + "T00:00:00.000Z", isCompany, vipPackage) + .then(function (res) { + var _a; + setSuccess(((_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.message) || "Inquiry created successfully!"); + setDescription(""); + setPackageWidth(0); + setPackageHeight(0); + setPackageDepth(0); + setPackageWeight(0); + setSourceAddressCity(""); + setSourceAddressBuildingNumber(""); + setSourceAddressApartmentNumber(""); + setSourceAddressCity(""); + setSourceAddressZipCode(""); + setSourceAddressCountry(""); + setDestinationAddressCity(""); + setDestinationAddressBuildingNumber(""); + setDestinationAddressApartmentNumber(""); + setDestinationAddressCity(""); + setDestinationAddressZipCode(""); + setDestinationAddressCountry(""); + setPickupDate(""); + setDeliveryDate(""); + setPriority("low"); + setAtWeekend(false); + setIsCompany(false); + setVipPackage(false); + })["catch"](function (err) { + var _a, _b; + setError(((_b = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || "Something went wrong!"); + })["finally"](function () { + setInquiryLoading(false); + }); + }; + return (react_1["default"].createElement(react_1["default"].Fragment, null, + loading ? react_1["default"].createElement(loader_1.Loader, null) : null, + react_1["default"].createElement("div", { className: "container mx-auto px-4" }, + react_1["default"].createElement(header_1.Header, { loading: loading, setLoading: setLoading }), + react_1["default"].createElement("h1", { className: "mb-2 text-3xl font-bold text-gray-900 dark:text-white" }, "Create an inquiry"), + react_1["default"].createElement("p", { className: "mb-5" }, "Please fill all fields below and click blue button located at the bottom of this page."), + react_1["default"].createElement("form", { className: "flex flex-col gap-6 px-10", onSubmit: onSubmit }, + react_1["default"].createElement("div", { className: " gap-6" }, + react_1["default"].createElement(SectionTitle, { title: "Package Details" }), + react_1["default"].createElement(PackageDetailsSection, __assign({}, { packageWidth: packageWidth, setPackageWidth: setPackageWidth, packageHeight: packageHeight, setPackageHeight: setPackageHeight, packageDepth: packageDepth, setPackageDepth: setPackageDepth, packageWeight: packageWeight, setPackageWeight: setPackageWeight })), + react_1["default"].createElement(SectionTitle, { title: "Source Address" }), + react_1["default"].createElement(AddressSection, { prefix: "source", street: sourceAddressStreet, setStreet: setSourceAddressStreet, buildingNumber: sourceAddressBuildingNumber, setBuildingNumber: setSourceAddressBuildingNumber, apartmentNumber: sourceAddressApartmentNumber, setApartmentNumber: setSourceAddressApartmentNumber, city: sourceAddressCity, setCity: setSourceAddressCity, zipCode: sourceAddressZipCode, setZipCode: setSourceAddressZipCode, country: sourceAddressCountry, setCountry: setSourceAddressCountry }), + react_1["default"].createElement(SectionTitle, { title: "Destination Address" }), + react_1["default"].createElement(AddressSection, { prefix: "destination", street: destinationAddressStreet, setStreet: setDestinationAddressStreet, buildingNumber: destinationAddressBuildingNumber, setBuildingNumber: setDestinationAddressBuildingNumber, apartmentNumber: destinationAddressApartmentNumber, setApartmentNumber: setDestinationAddressApartmentNumber, city: destinationAddressCity, setCity: setDestinationAddressCity, zipCode: destinationAddressZipCode, setZipCode: setDestinationAddressZipCode, country: destinationAddressCountry, setCountry: setDestinationAddressCountry }), + react_1["default"].createElement(SectionTitle, { title: "Delivery Details" }), + react_1["default"].createElement(DeliveryDetailsSection, __assign({}, { pickupDate: pickupDate, setPickupDate: setPickupDate, deliveryDate: deliveryDate, setDeliveryDate: setDeliveryDate, priority: priority, setPriority: setPriority, atWeekend: atWeekend, setAtWeekend: setAtWeekend, isCompany: isCompany, setIsCompany: setIsCompany, vipPackage: vipPackage, setVipPackage: setVipPackage })), + react_1["default"].createElement(ShortDescriptionSection, { description: description, setDescription: setDescription }), + react_1["default"].createElement(SubmitButton, { inquiryLoading: inquiryLoading }), + react_1["default"].createElement(Alerts, { error: error, success: success }))), + success ? (react_1["default"].createElement(flowbite_react_1.Alert, { color: "success", icon: hi_1.HiCheckCircle, className: "mb-3" }, + react_1["default"].createElement("span", null, + react_1["default"].createElement("span", { className: "font-bold" }, "Success!"), + " ", + success))) : null, + react_1["default"].createElement(footer_1.Footer, null)))); +} +exports["default"] = CreateInquiry; diff --git a/SwiftParcel.Web/frontend/src/pages/home.tsx b/SwiftParcel.Web/frontend/src/pages/home.tsx index 960b168..f84ed53 100644 --- a/SwiftParcel.Web/frontend/src/pages/home.tsx +++ b/SwiftParcel.Web/frontend/src/pages/home.tsx @@ -1,22 +1,24 @@ -import { Badge, Button, Datepicker, Select, Spinner, TextInput } from "flowbite-react"; +import { Badge, Button, Select, Spinner, TextInput } from "flowbite-react"; import React from "react"; import { BsBoxSeam } from "react-icons/bs"; import { HiExclamation } from "react-icons/hi"; import { Footer } from "../components/footer"; import { Header } from "../components/header"; import { Loader } from "../components/loader"; -import { getParcel } from "../utils/api"; +import { getOrder } from "../utils/api"; import { Link } from "react-router-dom"; +import { OrderDetailsModal } from "../components/modals/orders/orderDetailsModal"; export default function Home() { const [loading, setLoading] = React.useState(true); - const [trackingNumber, setTrackingNumber] = React.useState(""); + const [orderId, setOrderId] = React.useState(""); const [error, setError] = React.useState(false); const [errorText, setErrorText] = React.useState(""); - const [parcel, setParcel] = React.useState(null); - const [loadingParcel, setLoadingParcel] = React.useState(false); + const [order, setOrder] = React.useState(null); + const [loadingOrder, setLoadingOrder] = React.useState(false); + const [showOrderDetailsModal, setShowOrderDetailsModal] = React.useState(false); const handleAnonymousInquirySubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -25,38 +27,28 @@ export default function Home() { const onSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(false); - setLoadingParcel(true); - if (trackingNumber.startsWith("LT")) { - getParcel(trackingNumber) + setLoadingOrder(true); + getOrder(orderId) .then((res) => { if (res?.data) { - setParcel(res?.data); + setOrder(res?.data); } else { - setParcel(null); + setOrder(null); setError(true); - setErrorText("Parcel not found"); + setErrorText("Order not found"); } }) .catch((err) => { - setParcel(null); + setOrder(null); setError(true); - setErrorText("Parcel not found"); + setErrorText("Order not found"); }) .finally(() => { - setLoadingParcel(false); + setLoadingOrder(false); + setShowOrderDetailsModal(true); }); - return; - } - setTimeout(() => { - setTrackingNumber(""); - setError(true); - setErrorText("Invalid tracking number"); - setLoadingParcel(false); - }, 1000); }; - - return ( <> {loading ? : null} @@ -70,7 +62,7 @@ export default function Home() { Give your requirements and get many options to choose the best one.

- {parcel ? ( -
-

- Parcel Details -

-

- Here are the details of your parcel. -

-
-
-
-

- Tracking Number -

-

- {parcel.parcelNumber} -

-
-
-

Status

-

- - - {parcel.status} - - -

-
-
-
-
-

Sender

-

- {parcel.senderName} -

-
-
-

Receiver

-

- {parcel.receiverName} -

-
-
-
-
-

Weight

-

- {parcel.weight} kg -

-
-
-
-
-

Started

-

- {new Date(parcel.createdAt).toLocaleString("lt-LT")} -

-
-
-

- Last Update -

-

- {new Date(parcel.updatedAt).toLocaleString("lt-LT")} -

-
-
-
-
- ) : null} + + { order ? + + : null } +
diff --git a/SwiftParcel.Web/frontend/src/pages/inquiries.tsx b/SwiftParcel.Web/frontend/src/pages/inquiries.tsx deleted file mode 100644 index 8f6cb9e..0000000 --- a/SwiftParcel.Web/frontend/src/pages/inquiries.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Table, Pagination } from "flowbite-react"; -import React from "react"; -import { Header } from "../components/header"; -import { Footer } from "../components/footer"; -import { getInquiries } from "../utils/api"; -import { Loader } from "../components/loader"; -import { ParcelDetails } from "../components/details/parcel"; - -export default function Inquiries() { - const [page, setPage] = React.useState(1); - const [inquiries, setInquiries] = React.useState(null); - - const [loadingHeader, setLoadingHeader] = React.useState(true); - const [loadingParcels, setLoadingParcels] = React.useState(true); - - React.useEffect(() => { - getInquiries() - .then((res) => { - if (res.status === 200) { - setInquiries(res?.data); - } else { - throw new Error(); - } - }) - .catch((err) => { - setInquiries(null); - }) - .finally(() => { - setLoadingParcels(false); - }); - }, [page]); - - const onPageChange = (page: number) => { - setPage(page); - }; - - return ( - <> - {loadingHeader || loadingParcels ? : null} -
-
-

- Parcels -

- - - # - Sender - Receiver - Weight - Price - - Deliver - - - - {inquiries != null && inquiries?.results?.length > 0 ? ( - inquiries?.results.map((parcel: any) => ( - - )) - ) : ( - - - - )} - -
- No parcels found -
- {inquiries != null && inquiries?.total_pages > 1 ? ( - - ) : null} -
-
- - ); -} diff --git a/SwiftParcel.Web/frontend/src/pages/inquiries/createInquiry.tsx b/SwiftParcel.Web/frontend/src/pages/inquiries/createInquiry.tsx new file mode 100644 index 0000000..a989880 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/inquiries/createInquiry.tsx @@ -0,0 +1,698 @@ +import { + Alert, + Button, + Checkbox, + Label, + Spinner, + TextInput, + } from "flowbite-react"; + import React from "react"; + import { HiCheckCircle } from "react-icons/hi"; + import { Footer } from "../../components/footer"; + import { Header } from "../../components/header"; + import { Loader } from "../../components/loader"; + import { createInquiry } from "../../utils/api"; +import stringToBoolean from "../../components/parsing/stringToBoolean"; +import booleanToString from "../../components/parsing/booleanToString"; +import { CourierOffers } from "../couriers/courierOffers"; +import { useNavigate } from "react-router-dom"; +import { getUserIdFromStorage } from "../../utils/storage"; + + +type FormFields = { + description: string; + packageWidth: number; + packageHeight: number; + packageDepth: number; + packageWeight: number; + sourceAddressStreet: string; + sourceAddressBuildingNumber: string; + sourceAddressApartmentNumber: string; + sourceAddressCity: string; + sourceAddressZipCode: string; + sourceAddressCountry: string; + destinationAddressStreet: string; + destinationAddressBuildingNumber: string; + destinationAddressApartmentNumber: string; + destinationAddressCity: string; + destinationAddressZipCode: string; + destinationAddressCountry: string; + pickupDate: string; + deliveryDate: string; + priority: string; + atWeekend: boolean; + isCompany: boolean; + vipPackage: boolean; +}; + + +type FormErrors = { + [K in keyof FormFields]?: string; +}; + +const TextInputWithLabel = ({ id, label, value, onChange, type, error }) => ( +
+ + onChange(e)} + className={`border-gray-300 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm rounded-md ${error ? 'border-red-500' : ''}`} + /> + {error &&

{error}

} +
+); + + +const DateInputWithLabel = ({ id, label, value, onChange, error }) => { + // Function to format the date to MongoDB format + const formatToMongoDBDate = (dateString) => { + const date = new Date(dateString); + return date.toISOString(); + }; + + // Function to handle date change + const handleDateChange = (e) => { + const formattedDate = formatToMongoDBDate(e.target.value); + console.log("FormattedDate is: ...", formattedDate) + onChange(formattedDate); + }; + + return ( +
+ + + {error &&

{error}

} +
+ ); +}; + + +const SectionTitle = ({ title }) => ( +
+

{title}

+
+); + +const Alerts = ({ error, success }) => ( + <> + {error && {error}} + {success && {success}} + +); + + +const SubmitButton = ({ inquiryLoading }) => ( +
+ +
+); + +const ShortDescriptionSection = ({ formFields, handleDescriptionChange, errors }) => ( +
+ +
+); + +const PackageDetailsSection = ({ formFields, handleNumberChange, errors }) => ( +
+ + + + +
+); + +const DeliveryDetailsSection = ({ formFields, handleDateChange, handlePriorityChange, handleBooleanChange, errors }) => ( +
+ {/* Other inputs for priority, atWeekend, isCompany, vipPackage */} + + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+); + +const AddressSection = ({ prefix, formFields, handleStringChange, errors}) => ( +
+ + + + + + +
+); + + +export default function CreateInquiry() { + const [showLoginModal, setShowLoginModal] = React.useState(true); + const [loading, setLoading] = React.useState(true); + const [formIsValid, setFormIsValid] = React.useState(true); + + // const [formFields, setFormFields] = React.useState({ + // description: "", + // packageWidth: 0, + // packageHeight: 0, + // packageDepth: 0, + // packageWeight: 0, + // sourceAddressStreet: "", + // sourceAddressBuildingNumber: "", + // sourceAddressApartmentNumber: "", + // sourceAddressCity: "", + // sourceAddressZipCode: "", + // sourceAddressCountry: "", + // destinationAddressStreet: "", + // destinationAddressBuildingNumber: "", + // destinationAddressApartmentNumber: "", + // destinationAddressCity: "", + // destinationAddressZipCode: "", + // destinationAddressCountry: "", + // pickupDate: "", + // deliveryDate: "", + // priority: "Low", + // atWeekend: false, + // isCompany: false, + // vipPackage: false + // }); + + + const [formFields, setFormFields] = React.useState({ + description: "sin", + packageWidth: 1, + packageHeight: 0.5, + packageDepth: 0.5, + packageWeight: 0.5, + sourceAddressStreet: "Plac politechniki", + sourceAddressBuildingNumber: "1", + sourceAddressApartmentNumber: "31", + sourceAddressCity: "Warszawa", + sourceAddressZipCode: "00-420", + sourceAddressCountry: "Polska", + destinationAddressStreet: "Koszykowa", + destinationAddressBuildingNumber: "21", + destinationAddressApartmentNumber: "37", + destinationAddressCity: "Warszawa", + destinationAddressZipCode: "00-420", + destinationAddressCountry: "Polska", + pickupDate: "2024-01-21", // Format this as per your requirement + deliveryDate: "2024-01-30", // Format this as per your requirement + priority: "High", + atWeekend: true, + isCompany: true, + vipPackage: true + }); + + + const handleNumberChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = parseFloat(event.target.value); + setFormFields(prevState => ({ + ...prevState, + [field]: isNaN(newValue) ? 0 : newValue + })); + }; + + const handleStringChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setFormFields(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handleDateChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event; + setFormFields(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handlePriorityChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = event.target.value; + setFormFields(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const handleBooleanChange = (field: T) => (event: React.ChangeEvent) => { + const newValue = stringToBoolean(event.target.value); + setFormFields(prevState => ({ + ...prevState, + [field]: newValue + })); + }; + + const [formErrors, setFormErrors] = React.useState({}); + + const [offers, setOffers] = React.useState(null); + const [showOffers, setShowOffers] = React.useState(false); // New state to control display + + const formatDateForServer = (dateString) => { + return new Date(dateString).toISOString(); // Adjust this based on server's expected format + }; + + const [error, setError] = React.useState(""); + const [success, setSuccess] = React.useState(""); + + const [inquiryLoading, setInquiryLoading] = React.useState(false); + + const navigate = useNavigate(); + + const validateForm = () => { + const errors: FormErrors = {}; + + // validation of description section + + if (!formFields.description) { + errors.description = "Short description is required!"; + } + + // validation of package details section + + if (formFields.packageWidth <= 0.2 || formFields.packageWidth >= 8) { + errors.packageWidth = "Width must be greater than 0.2 meters and less than 8 meters!"; + } + + if (formFields.packageHeight <= 0.2 || formFields.packageHeight >= 8) { + errors.packageHeight = "Height must be greater than 0.2 meters and less than 8 meters!"; + } + + if (formFields.packageDepth <= 0.2 || formFields.packageHeight >= 8) { + errors.packageDepth = "Depth must be greater than 0.2 meters and less than 8 meters!"; + } + + if (formFields.packageWeight <= 0.2 || formFields.packageWeight >= 8) { + errors.packageWeight = "Weight must be greater than 0.2 meters and less than 8 meters!"; + } + + // validation of source address section + + if (!formFields.sourceAddressStreet) { + errors.sourceAddressStreet = "Street in source address is required!"; + } + + if (!formFields.sourceAddressBuildingNumber) { + errors.sourceAddressBuildingNumber = "Building number in source address is required!"; + } + + if (!formFields.sourceAddressCity) { + errors.sourceAddressCity = "City in source address is required!"; + } + + if (!formFields.sourceAddressZipCode) { + errors.sourceAddressZipCode = "Zip code in source address is required!"; + } + + if (!formFields.sourceAddressCountry) { + errors.sourceAddressCountry = "Country in source address is required!"; + } + + // validation of destination address section + + if (!formFields.destinationAddressStreet) { + errors.destinationAddressStreet = "Street in destination address is required!"; + } + + if (!formFields.destinationAddressBuildingNumber) { + errors.destinationAddressBuildingNumber = "Building number in destination address is required!"; + } + + if (!formFields.destinationAddressCity) { + errors.destinationAddressCity = "City in destination address is required!"; + } + + if (!formFields.destinationAddressZipCode) { + errors.destinationAddressZipCode = "Zip code in destination address is required!"; + } + + if (!formFields.destinationAddressCountry) { + errors.destinationAddressCountry = "Country in destination address is required!"; + } + + // validation of pickup date and delivery date + + const currentDate = new Date(); + const pickupDate = new Date(formFields.pickupDate); + const deliveryDate = new Date(formFields.deliveryDate); + + if (currentDate >= pickupDate) + { + errors.pickupDate = "Pickup date must be later than current date!"; + } + + if (pickupDate >= deliveryDate) + { + errors.deliveryDate = "Delivery date must be later than pickup date!"; + } + + setFormErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleSelectOffer = (offer) => { + // Logic to proceed with the selected offer + console.log("Selected Offer: ", offer); + // Additional logic here + }; + + const onSubmit = (e: any) => { + e.preventDefault(); + + const isFormValid = validateForm(); + setFormIsValid(isFormValid); + + if (!isFormValid) { + return; + } + + if (inquiryLoading) return; + setError(""); + setSuccess(""); + setInquiryLoading(true); + + createInquiry( + getUserIdFromStorage(), + formFields.description, + formFields.packageWidth, + formFields.packageHeight, + formFields.packageDepth, + formFields.packageWeight, + formFields.sourceAddressStreet, + formFields.sourceAddressBuildingNumber, + formFields.sourceAddressApartmentNumber, + formFields.sourceAddressCity, + formFields.sourceAddressZipCode, + formFields.sourceAddressCountry, + formFields.destinationAddressStreet, + formFields.destinationAddressBuildingNumber, + formFields.destinationAddressApartmentNumber, + formFields.destinationAddressCity, + formFields.destinationAddressZipCode, + formFields.destinationAddressCountry, + formFields.priority, + formFields.atWeekend, + formatDateForServer(formFields.pickupDate), + formatDateForServer(formFields.deliveryDate), + formFields.isCompany, + formFields.vipPackage) + .then((response) => { + setSuccess("Inquiry created successfully!"); + setFormFields( + { + description: "", + packageWidth: 0, + packageHeight: 0, + packageDepth: 0, + packageWeight: 0, + sourceAddressStreet: "", + sourceAddressBuildingNumber: "", + sourceAddressApartmentNumber: "", + sourceAddressCity: "", + sourceAddressZipCode: "", + sourceAddressCountry: "", + destinationAddressStreet: "", + destinationAddressBuildingNumber: "", + destinationAddressApartmentNumber: "", + destinationAddressCity: "", + destinationAddressZipCode: "", + destinationAddressCountry: "", + pickupDate: "", + deliveryDate: "", + priority: "Low", + atWeekend: false, + isCompany: false, + vipPackage: false + }); + + navigate("/offers", {state: {parcelId: response}}); + + // setOffers(offers); + // if (offers && offers.length > 0) { + // setShowOffers(true); // Show offers if available + // } else { + // setShowOffers(false); // Hide offers if none are available + // } + // if (offers) { + // // TODO: Implement logic to display the offers + // } + }) + + .catch((err) => { + setError(err?.response?.data?.reason || "Something went wrong!"); + }) + .finally(() => { + setInquiryLoading(false); + }); + }; + + // const clickSeeOffers = (data) => { + // navigate.push("/offers", {data: data}); + // } + + return ( + <> + {loading ? : null} + + {showOffers ? ( + <> +

Select a Courier Offer

+ + + ) : ( +
+
+

+ Create an inquiry +

+

+ Please fill all fields below and click blue button located at the bottom of this page. +

+ +
+ +
+ + + + + + + + + + + + + + + + + + {!formIsValid &&

Please fill in all required fields.

} + + + + + +
+
+ {success ? ( +
+ + + Success! {success} + + + +
+ ) : null} +
+
+ )} + + + ); + } + \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/inquiries/inquiries.tsx b/SwiftParcel.Web/frontend/src/pages/inquiries/inquiries.tsx new file mode 100644 index 0000000..4bd44ee --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/inquiries/inquiries.tsx @@ -0,0 +1,229 @@ +import { Table, Pagination, Button } from "flowbite-react"; +import React from "react"; +import { Header } from "../../components/header"; +import { Footer } from "../../components/footer"; +import { getInquiriesUser, getInquiriesOfficeWorker } from "../../utils/api"; +import { Loader } from "../../components/loader"; +import { InquiryDetails } from "../../components/details/inquiry"; +import { isPackageValid } from "../../components/details/inquiry"; +import { FilterInquiriesModal } from "../../components/modals/inquiries/filterInquiriesModal"; +import { getUserIdFromStorage, getUserInfo } from "../../utils/storage"; + +export default function Inquiries() { + const [page, setPage] = React.useState(1); + const [inputData, setInputData] = React.useState(null); + const [tableData, setTableData] = React.useState(null); + const [role, setRole] = React.useState(null); + + const [sortedColumn, setSortedColumn] = React.useState(null); + const [sortDirection, setSortDirection] = React.useState('ascending'); + + const [showFilterInquiriesModal, setShowFilterInquiriesModal] = React.useState(false); + + const [loadingHeader, setLoadingHeader] = React.useState(true); + const [loadingInquiries, setLoadingInquiries] = React.useState(true); + + React.useEffect(() => { + ((getUserInfo().role === "officeworker") ? getInquiriesOfficeWorker() : getInquiriesUser(getUserIdFromStorage())) + .then((res) => { + if (res.status === 200) { + setInputData(res?.data); + setTableData(res?.data); + setRole(getUserInfo().role); + } else { + throw new Error(); + } + }) + .catch((err) => { + setInputData(null); + setTableData(null); + }) + .finally(() => { + setLoadingInquiries(false); + }); + }, [page]); + + const onPageChange = (page: number) => { + setPage(page); + }; + + const handleSort = (column : string) => { + let direction = 'ascending'; + if (sortedColumn === column && sortDirection === 'ascending') { + direction = 'descending'; + } + + const sortedData = [...tableData].sort((a, b) => { + switch (column) { + case 'id': { + const idA = a.id; + const idB = b.id; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'packageDimensions': { + const volumeA = a.width * a.height * a.depth; + const volumeB = b.width * b.height * b.depth; + return direction === 'ascending' ? volumeA - volumeB : volumeB - volumeA; + } + case 'packageWeight': { + const weightA = a.weight; + const weightB = b.weight; + return direction === 'ascending' ? weightA - weightB : weightB - weightA; + } + case 'sourceAddress': { + const addressA = `${a.source.street} ${a.source.buildingNumber} ${a.source.apartmentNumber} + ${a.source.zipCode} ${a.source.city} ${a.source.country}`; + const addressB = `${b.source.street} ${b.source.buildingNumber} ${b.source.apartmentNumber} + ${b.source.zipCode} ${b.source.city} ${b.source.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + case 'destinationAddress': { + const addressA = `${a.destination.street} ${a.destination.buildingNumber} ${a.destination.apartmentNumber} + ${a.destination.zipCode} ${a.destination.city} ${a.destination.country}`; + const addressB = `${b.destination.street} ${b.destination.buildingNumber} ${b.destination.apartmentNumber} + ${b.destination.zipCode} ${b.destination.city} ${b.destination.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + case 'dateOfInquiring': { + const dateA = new Date(a.createdAt); + const dateB = new Date(b.createdAt); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'status': { + const statusA = isPackageValid(a.validTo) ? "valid" : "expired"; + const statusB = isPackageValid(b.validTo) ? "valid" : "expired"; + if (direction === 'ascending') { + return statusA > statusB ? 1 : -1; + } + else { + return statusA < statusB ? 1 : -1; + } + } + default: + return 0; + } + }); + + setSortedColumn(column); + setSortDirection(direction); + setTableData(sortedData); + }; + + const getSortIcon = (column : string) => { + return sortedColumn === column ? (sortDirection === 'ascending' ? '▲' : '▼') : ''; + } + + const tableHeaderStyle = { + row: { + display: 'flex', + justifyContent: 'space-between', + }, + left: { + alignSelf: 'flex-start', + }, + right: { + alignSelf: 'flex-end', + }, + }; + + return ( + <> + {loadingHeader || loadingInquiries ? : null} +
+
+
+

+ {role == "officeworker" ? 'Bank inquiries' : 'Your inquiries'} +

+ +
+

+ To see details of an inquiry or check its full status, click button in the last column of the table. +

+ + + + handleSort('id')}> + Id {getSortIcon('id')} + + handleSort('packageDimensions')}> + Package dimensions {getSortIcon('packageDimensions')} + + handleSort('packageWeight')}> + Package weight {getSortIcon('packageWeight')} + + handleSort('sourceAddress')}> + Source address {getSortIcon('sourceAddress')} + + handleSort('destinationAddress')}> + Destination address {getSortIcon('destinationAddress')} + + handleSort('dateOfInquiring')}> + Date of inquiring {getSortIcon('dateOfInquiring')} + + handleSort('status')}> + Status {getSortIcon('status')} + + + Details + + + + {tableData != null && tableData?.length > 0 ? ( + tableData?.map((inquiry: any) => ( + + )) + ) : ( + + + + )} + +
+ No inquiries found +
+ {tableData != null && tableData?.total_pages > 1 ? ( + + ) : null} +
+
+ + ); +} diff --git a/SwiftParcel.Web/frontend/src/pages/inquiry.tsx b/SwiftParcel.Web/frontend/src/pages/inquiry.tsx deleted file mode 100644 index 5abad8a..0000000 --- a/SwiftParcel.Web/frontend/src/pages/inquiry.tsx +++ /dev/null @@ -1,489 +0,0 @@ -import { - Alert, - Button, - Checkbox, - Label, - Spinner, - TextInput, - } from "flowbite-react"; - import React from "react"; - import { HiInformationCircle, HiCheckCircle } from "react-icons/hi"; - import { Footer } from "../components/footer"; - import { Header } from "../components/header"; - import { Loader } from "../components/loader"; - import { createInquiry, register } from "../utils/api"; -import stringToBoolean from "../components/parsing/stringToBoolean"; -import booleanToString from "../components/parsing/booleanToString"; - -export default function Inquiry() { - const [loading, setLoading] = React.useState(true); - - const [description, setDescription] = React.useState(""); - const [packageWidth, setPackageWidth] = React.useState(0); - const [packageHeight, setPackageHeight] = React.useState(0); - const [packageDepth, setPackageDepth] = React.useState(0); - const [packageWeight, setPackageWeight] = React.useState(0); - - const [sourceAddressStreet, setSourceAddressStreet] = React.useState(""); - const [sourceAddressBuildingNumber, setSourceAddressBuildingNumber] = React.useState(""); - const [sourceAddressApartmentNumber, setSourceAddressApartmentNumber] = React.useState(""); - const [sourceAddressCity, setSourceAddressCity] = React.useState(""); - const [sourceAddressZipCode, setSourceAddressZipCode] = React.useState(""); - const [sourceAddressCountry, setSourceAddressCountry] = React.useState(""); - - const [destinationAddressStreet, setDestinationAddressStreet] = React.useState(""); - const [destinationAddressBuildingNumber, setDestinationAddressBuildingNumber] = React.useState(""); - const [destinationAddressApartmentNumber, setDestinationAddressApartmentNumber] = React.useState(""); - const [destinationAddressCity, setDestinationAddressCity] = React.useState(""); - const [destinationAddressZipCode, setDestinationAddressZipCode] = React.useState(""); - const [destinationAddressCountry, setDestinationAddressCountry] = React.useState(""); - - const [pickupDate, setPickupDate] = React.useState(""); - const [deliveryDate, setDeliveryDate] = React.useState(""); - const [priority, setPriority] = React.useState("low"); - const [atWeekend, setAtWeekend] = React.useState(false); - const [isCompany, setIsCompany] = React.useState(false); - const [vipPackage, setVipPackage] = React.useState(false); - - const [error, setError] = React.useState(""); - const [success, setSuccess] = React.useState(""); - - const [inquiryLoading, setInquiryLoading] = React.useState(false); - - const onSubmit = (e: any) => { - e.preventDefault(); - if (inquiryLoading) return; - setError(""); - setSuccess(""); - setInquiryLoading(true); - - createInquiry(description, packageWidth, packageHeight, packageDepth, packageWeight, - sourceAddressStreet, sourceAddressBuildingNumber, sourceAddressApartmentNumber, - sourceAddressCity, sourceAddressZipCode, sourceAddressCountry, - destinationAddressStreet, destinationAddressBuildingNumber, destinationAddressApartmentNumber, - destinationAddressCity, destinationAddressZipCode, destinationAddressCountry, priority, atWeekend, - `${pickupDate}T00:00:00.000Z`, `${deliveryDate}T00:00:00.000Z`, isCompany, vipPackage) - .then((res) => { - setSuccess( - res?.data?.message || "Inquiry created successfully!" - ); - setDescription(""); - setPackageWidth(0); - setPackageHeight(0); - setPackageDepth(0); - setPackageWeight(0); - setSourceAddressCity(""); - setSourceAddressBuildingNumber(""); - setSourceAddressApartmentNumber(""); - setSourceAddressCity(""); - setSourceAddressZipCode(""); - setSourceAddressCountry(""); - setDestinationAddressCity(""); - setDestinationAddressBuildingNumber(""); - setDestinationAddressApartmentNumber(""); - setDestinationAddressCity(""); - setDestinationAddressZipCode(""); - setDestinationAddressCountry(""); - setPickupDate(""); - setDeliveryDate(""); - setPriority("low"); - setAtWeekend(false); - setIsCompany(false); - setVipPackage(false); - }) - .catch((err) => { - setError(err?.response?.data?.message || "Something went wrong!"); - }) - .finally(() => { - setInquiryLoading(false); - }); - }; - - return ( - <> - {loading ? : null} -
-
-

- Create an inquiry -

-

- Please fill all fields below and click blue button located at the bottom of this page. -

- -
- -
- -
-
- -
-
-
- setPackageWidth(parseFloat(e.target.value))} - /> -
-
-
-
- setPackageHeight(parseFloat(e.target.value))} - /> -
-
-
-
- setPackageDepth(parseFloat(e.target.value))} - /> -
-
-
-
- setPackageWeight(parseFloat(e.target.value))} - /> -
-
- -
- -
-
- -
- -
-
-
-
- setSourceAddressStreet(e.target.value)} - /> -
-
-
-
- setSourceAddressBuildingNumber(e.target.value)} - /> -
-
-
-
- setSourceAddressApartmentNumber(e.target.value)} - /> -
-
- -
-
-
-
- setSourceAddressCity(e.target.value)} - /> -
-
-
-
- setSourceAddressZipCode(e.target.value)} - /> -
-
-
-
- setSourceAddressCountry(e.target.value)} - /> -
-
-
-
- -
- -
-
- -
-
-
-
-
- setDestinationAddressStreet(e.target.value)} - /> -
-
-
-
- setDestinationAddressBuildingNumber(e.target.value)} - /> -
-
-
-
- setDestinationAddressApartmentNumber(e.target.value)} - /> -
-
- -
-
-
-
- setDestinationAddressCity(e.target.value)} - /> -
-
-
-
- setDestinationAddressZipCode(e.target.value)} - /> -
-
-
-
- setDestinationAddressCountry(e.target.value)} - /> -
-
-
-
- -
-
-
-
- setPickupDate(e.target.value)} - /> -
-
-
-
- setDeliveryDate(e.target.value)} - /> -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
- -
-
-
- setDescription(e.target.value)} - /> -
- - {inquiryLoading ? ( - - ) : ( - - )} -
- - {error ? ( - - - Error! {error} - - - ) : null} - {success ? ( - - - Success! {success} - - - ) : null} -
-
- - ); - } - \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/offers/offerRequests.tsx b/SwiftParcel.Web/frontend/src/pages/offers/offerRequests.tsx new file mode 100644 index 0000000..d1c0fff --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/offers/offerRequests.tsx @@ -0,0 +1,5 @@ +import OffersOfficeWorker from "./offersOfficeWorker"; + +export default function OfferRequests() { + return OffersOfficeWorker("offer-requests"); +} \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/offers/offersOfficeWorker.tsx b/SwiftParcel.Web/frontend/src/pages/offers/offersOfficeWorker.tsx new file mode 100644 index 0000000..dde80cc --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/offers/offersOfficeWorker.tsx @@ -0,0 +1,256 @@ +import { Table, Pagination, Button } from "flowbite-react"; +import React from "react"; +import { Header } from "../../components/header"; +import { Footer } from "../../components/footer"; +import { getOfferRequests, getPendingOffers } from "../../utils/api"; +import { Loader } from "../../components/loader"; +import { OfferRequestDetails } from "../../components/details/offerRequest"; +import { FilterOfferRequestsModal } from "../../components/modals/offers/filterOfferRequestsModal"; + +export default function OffersOfficeWorker(pageContent: string) { + const [page, setPage] = React.useState(1); + const [inputData, setInputData] = React.useState(null); + const [tableData, setTableData] = React.useState(null); + + const [sortedColumn, setSortedColumn] = React.useState(null); + const [sortDirection, setSortDirection] = React.useState('ascending'); + + const [showFilterOfferRequestsModal, setShowFilterOfferRequestsModal] = React.useState(false); + + const [loadingHeader, setLoadingHeader] = React.useState(true); + const [loadingOfferRequests, setLoadingOfferRequests] = React.useState(true); + + React.useEffect(() => { + + ((pageContent == "offer-requests") ? getOfferRequests() : getPendingOffers()) + .then((res) => { + if (res.status === 200) { + setInputData(res?.data); + setTableData(res?.data); + } else { + throw new Error(); + } + }) + .catch((err) => { + setInputData(null); + setTableData(null); + }) + .finally(() => { + setLoadingOfferRequests(false); + }); + + }, [page]); + + const onPageChange = (page: number) => { + setPage(page); + }; + + const handleSort = (column : string) => { + let direction = 'ascending'; + if (sortedColumn === column && sortDirection === 'ascending') { + direction = 'descending'; + } + + const sortedData = [...tableData].sort((a, b) => { + switch (column) { + case 'id': { + const idA = a.id; + const idB = b.id; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'customerId': { + const idA = a.customerId; + const idB = b.customerId; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'status': { + const idA = a.status; + const idB = b.status; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'orderRequestDate': { + const dateA = new Date(a.orderRequestDate); + const dateB = new Date(b.orderRequestDate); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'requestValidTo': { + const dateA = new Date(a.requestValidTo); + const dateB = new Date(b.requestValidTo); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'buyerName': { + const idA = a.buyerName; + const idB = b.buyerName; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'buyerEmail': { + const idA = a.buyerEmail; + const idB = b.buyerEmail; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'buyerAddress': { + const addressA = `${a.buyerAddress.street} ${a.buyerAddress.buildingNumber} ${a.buyerAddress.apartmentNumber} + ${a.buyerAddress.zipCode} ${a.buyerAddress.city} ${a.buyerAddress.country}`; + const addressB = `${b.buyerAddress.street} ${b.buyerAddress.buildingNumber} ${b.buyerAddress.apartmentNumber} + ${b.buyerAddress.zipCode} ${b.buyerAddress.city} ${b.buyerAddress.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + default: + return 0; + } + }); + + setSortedColumn(column); + setSortDirection(direction); + setTableData(sortedData); + }; + + const getSortIcon = (column : string) => { + return sortedColumn === column ? (sortDirection === 'ascending' ? '▲' : '▼') : ''; + } + + const tableHeaderStyle = { + row: { + display: 'flex', + justifyContent: 'space-between', + }, + left: { + alignSelf: 'flex-start', + }, + right: { + alignSelf: 'flex-end', + }, + }; + + return ( + <> + {loadingHeader || loadingOfferRequests ? : null} +
+
+
+

+ {pageContent == "offer-requests" ? 'Bank offer requests' : 'Manage pending offers'} +

+ + +
+ { pageContent == "pending-offers" ? +

+ To accept of reject an offer request, open details by clicking button in the last column, then scroll down. +

+ : +

+ To see details of an offer request or check its full status, click button in the last column of the table. +

+ } + + + + handleSort('id')}> + Id {getSortIcon('id')} + + + Inquiry + + handleSort('status')}> + Status {getSortIcon('status')} + + handleSort('orderRequestDate')}> + Order request date {getSortIcon('orderRequestDate')} + + handleSort('requestValidTo')}> + Request valid to date {getSortIcon('requestValidTo')} + + handleSort('buyerName')}> + Buyer name {getSortIcon('buyerName')} + + handleSort('buyerEmail')}> + Buyer email {getSortIcon('buyerEmail')} + + handleSort('buyerAddress')}> + Buyer address {getSortIcon('buyerAddress')} + + + Details + + + + {tableData != null && tableData?.length > 0 ? ( + tableData?.map((offerDetails: any) => ( + + )) + ) : ( + + + + )} + +
+ No {pageContent == "offer-requests" ? 'offer requests' : 'pending offers'} found +
+ {tableData != null && tableData?.total_pages > 1 ? ( + + ) : null} +
+
+ + ); +} diff --git a/SwiftParcel.Web/frontend/src/pages/offers/offersUser.tsx b/SwiftParcel.Web/frontend/src/pages/offers/offersUser.tsx new file mode 100644 index 0000000..6db084a --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/offers/offersUser.tsx @@ -0,0 +1,199 @@ +import { Table, Pagination, Button } from "flowbite-react"; +import React from "react"; +import { Header } from "../../components/header"; +import { Footer } from "../../components/footer"; +import { getCustomerData, getOffers } from "../../utils/api"; +import { Loader } from "../../components/loader"; +import { OfferDetails } from "../../components/details/offer"; +import { useLocation } from "react-router-dom"; +import { getUserIdFromStorage } from "../../utils/storage"; + +export default function Offers() { + const [page, setPage] = React.useState(1); + const [data, setData] = React.useState(null); + + const location = useLocation(); + + const [sortedColumn, setSortedColumn] = React.useState(null); + const [sortDirection, setSortDirection] = React.useState('ascending'); + + const [loadingHeader, setLoadingHeader] = React.useState(true); + const [loadingOffers, setLoadingOffers] = React.useState(true); + + const [userData, setUserData] = React.useState({fullName: "", email: "", address: ""}); + + React.useEffect(() => { + if (getUserIdFromStorage() !== null) { + getCustomerData(getUserIdFromStorage()) + .then((res) => { + if (res.status === 200) { + setUserData(res?.data); + } else { + throw new Error(); + } + }) + .catch((err) => { + }) + } + }, [page]); + + React.useEffect(() => { + getOffers(location.state.parcelId) + .then((res) => { + if (res.status === 200) { + setData(res?.data); + } else { + throw new Error(); + } + }) + .catch((err) => { + setData(null); + }) + .finally(() => { + setLoadingOffers(false); + }); + }, [page]); + + const onPageChange = (page: number) => { + setPage(page); + }; + + const handleSort = (column : string) => { + let direction = 'ascending'; + if (sortedColumn === column && sortDirection === 'ascending') { + direction = 'descending'; + } + + const sortedData = [...data].sort((a, b) => { + if (a == null && b == null) { + return 0; + } + if (a == null) { + return 1; + } + if (b == null) { + return -1; + } + + switch (column) { + case 'inquiryId': { + const idA = a.parcelId; + const idB = b.parcelId; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'totalPrice': { + const priceA = a.totalPrice; + const priceB = b.totalPrice; + return direction === 'ascending' ? priceA - priceB : priceB - priceA; + } + case 'expiringAt': { + const dateA = new Date(a.expiringAt); + const dateB = new Date(b.expiringAt); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'companyName': { + const nameA = a.companyName; + const nameB = b.companyName; + if (direction === 'ascending') { + return nameA > nameB ? 1 : -1; + } + else { + return nameA < nameB ? 1 : -1; + } + } + default: + return 0; + } + }); + + setSortedColumn(column); + setSortDirection(direction); + setData(sortedData); + }; + + const getSortIcon = (column : string) => { + return sortedColumn === column ? (sortDirection === 'ascending' ? '▲' : '▼') : ''; + } + + const tableHeaderStyle = { + row: { + display: 'flex', + justifyContent: 'space-between', + }, + left: { + alignSelf: 'flex-start', + }, + right: { + alignSelf: 'flex-end', + }, + }; + + return ( + <> + {loadingHeader || loadingOffers ? : null} +
+
+
+

+ Offers +

+
+ + + handleSort('inquiryId')}> + Inquiry's Id {getSortIcon('inquiryId')} + + handleSort('totalPrice')}> + Total price {getSortIcon('totalPrice')} + + handleSort('expiringAt')}> + Expiring date {getSortIcon('expiringAt')} + + handleSort('companyName')}> + Company name {getSortIcon('companyName')} + + + + + + + {data != null && data?.length > 0 ? ( + data?.map((offer: any) => offer != null ? ( + + ) : null) + ) : ( + + + + )} + +
+ No offers found +
+ {data != null && data?.total_pages > 1 ? ( + + ) : null} +
+
+ + ); +} diff --git a/SwiftParcel.Web/frontend/src/pages/offers/pendingOffers.tsx b/SwiftParcel.Web/frontend/src/pages/offers/pendingOffers.tsx new file mode 100644 index 0000000..22fe34d --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/offers/pendingOffers.tsx @@ -0,0 +1,5 @@ +import OffersOfficeWorker from "./offersOfficeWorker"; + +export default function PendingOffers() { + return OffersOfficeWorker("pending-offers"); +} \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/pages/orders/orders.tsx b/SwiftParcel.Web/frontend/src/pages/orders/orders.tsx new file mode 100644 index 0000000..bdb8d11 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/pages/orders/orders.tsx @@ -0,0 +1,248 @@ +import { Table, Pagination, Button } from "flowbite-react"; +import React from "react"; +import { Header } from "../../components/header"; +import { Footer } from "../../components/footer"; +import { getOrdersUser } from "../../utils/api"; +import { Loader } from "../../components/loader"; +import { OrderDetails } from "../../components/details/order"; +import { FilterOrdersModal } from "../../components/modals/orders/filterOrdersModal"; +import { getUserIdFromStorage } from "../../utils/storage"; +import { AddOrderByIdModal } from "../../components/modals/orders/addOrderByIdModal"; + +export default function Orders() { + const [page, setPage] = React.useState(1); + const [inputData, setInputData] = React.useState(null); + const [tableData, setTableData] = React.useState(null); + + const [sortedColumn, setSortedColumn] = React.useState(null); + const [sortDirection, setSortDirection] = React.useState('ascending'); + + const [showAddOrderByIdModal, setShowAddOrderByIdModal] = React.useState(false); + const [showFilterOrdersModal, setShowFilterOrdersModal] = React.useState(false); + + const [loadingHeader, setLoadingHeader] = React.useState(true); + const [loadingOfferRequests, setLoadingOfferRequests] = React.useState(true); + + React.useEffect(() => { + + getOrdersUser(getUserIdFromStorage()) + .then((res) => { + if (res.status === 200) { + setInputData(res?.data); + setTableData(res?.data); + } else { + throw new Error(); + } + }) + .catch((err) => { + setInputData(null); + setTableData(null); + }) + .finally(() => { + setLoadingOfferRequests(false); + }); + + }, [page]); + + const onPageChange = (page: number) => { + setPage(page); + }; + + const handleSort = (column : string) => { + let direction = 'ascending'; + if (sortedColumn === column && sortDirection === 'ascending') { + direction = 'descending'; + } + + const sortedData = [...tableData].sort((a, b) => { + switch (column) { + case 'id': { + const idA = a.id; + const idB = b.id; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'sourceAddress': { + const addressA = `${a.parcel.source.street} ${a.parcel.source.buildingNumber} ${a.parcel.source.apartmentNumber} + ${a.parcel.source.zipCode} ${a.parcel.source.city} ${a.parcel.source.country}`; + const addressB = `${b.parcel.source.street} ${b.parcel.source.buildingNumber} ${b.parcel.source.apartmentNumber} + ${b.parcel.source.zipCode} ${b.parcel.source.city} ${b.parcel.source.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + case 'destinationAddress': { + const addressA = `${a.parcel.destination.street} ${a.parcel.destination.buildingNumber} ${a.parcel.destination.apartmentNumber} + ${a.parcel.destination.zipCode} ${a.parcel.destination.city} ${a.parcel.destination.country}`; + const addressB = `${b.parcel.destination.street} ${b.parcel.destination.buildingNumber} ${b.parcel.destination.apartmentNumber} + ${b.parcel.destination.zipCode} ${b.parcel.destination.city} ${b.parcel.destination.country}`; + if (direction === 'ascending') { + return addressA > addressB ? 1 : -1; + } + else { + return addressA < addressB ? 1 : -1; + } + } + case 'company': { + const idA = a.company; + const idB = b.company; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + case 'orderRequestDate': { + const dateA = new Date(a.orderRequestDate); + const dateB = new Date(b.orderRequestDate); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'requestValidTo': { + const dateA = new Date(a.requestValidTo); + const dateB = new Date(b.requestValidTo); + if (direction === 'ascending') { + return dateA > dateB ? 1 : -1; + } + else { + return dateA < dateB ? 1 : -1; + } + } + case 'status': { + const idA = a.status; + const idB = b.status; + if (direction === 'ascending') { + return idA > idB ? 1 : -1; + } + else { + return idA < idB ? 1 : -1; + } + } + default: + return 0; + } + }); + + setSortedColumn(column); + setSortDirection(direction); + setTableData(sortedData); + }; + + const getSortIcon = (column : string) => { + return sortedColumn === column ? (sortDirection === 'ascending' ? '▲' : '▼') : ''; + } + + const tableHeaderStyle = { + row: { + display: 'flex', + justifyContent: 'space-between', + }, + left: { + alignSelf: 'flex-start', + }, + right: { + alignSelf: 'flex-end', + }, + }; + + return ( + <> + {loadingHeader || loadingOfferRequests ? : null} +
+
+
+

+ Your orders +

+
+ + +
+
+

+ To see details of an order or check its full status, click button in the last column of the table. +

+ + + + + handleSort('id')}> + Order Id {getSortIcon('id')} + + + Inquiry + + handleSort('sourceAddress')}> + Source address {getSortIcon('sourceAddress')} + + handleSort('destinationAddress')}> + Destination address {getSortIcon('destinationAddress')} + + handleSort('company')}> + Company {getSortIcon('company')} + + handleSort('orderRequestDate')}> + Order request date {getSortIcon('orderRequestDate')} + + handleSort('status')}> + Status {getSortIcon('status')} + + + Details + + + + {tableData != null && tableData?.length > 0 ? ( + tableData?.map((order: any) => ( + + )) + ) : ( + + + + )} + +
+ No orders found +
+ {tableData != null && tableData?.total_pages > 1 ? ( + + ) : null} +
+
+ + ); +} diff --git a/SwiftParcel.Web/frontend/src/pages/parcels/parcels.tsx b/SwiftParcel.Web/frontend/src/pages/parcels/parcels.tsx index da1dabc..dd3f82a 100644 --- a/SwiftParcel.Web/frontend/src/pages/parcels/parcels.tsx +++ b/SwiftParcel.Web/frontend/src/pages/parcels/parcels.tsx @@ -2,7 +2,7 @@ import { Table, Pagination } from "flowbite-react"; import React from "react"; import { Header } from "../../components/header"; import { Footer } from "../../components/footer"; -import { getParcels } from "../../utils/api"; +import { getDeliveries } from "../../utils/api"; import { Loader } from "../../components/loader"; import { ParcelDetails } from "../../components/details/parcel"; @@ -14,7 +14,7 @@ export default function Parcels() { const [loadingParcels, setLoadingParcels] = React.useState(true); React.useEffect(() => { - getParcels(page) + getDeliveries(page) .then((res) => { if (res.status === 200) { setParcels(res?.data); diff --git a/SwiftParcel.Web/frontend/src/pages/register.tsx b/SwiftParcel.Web/frontend/src/pages/register.tsx index 9032f37..006f9e1 100644 --- a/SwiftParcel.Web/frontend/src/pages/register.tsx +++ b/SwiftParcel.Web/frontend/src/pages/register.tsx @@ -11,7 +11,7 @@ import { HiInformationCircle, HiCheckCircle } from "react-icons/hi"; import { Footer } from "../components/footer"; import { Header } from "../components/header"; import { Loader } from "../components/loader"; -import { register } from "../utils/api"; +import { register, completeCustomerRegistration } from "../utils/api"; export default function Register() { const [loading, setLoading] = React.useState(true); @@ -21,14 +21,29 @@ export default function Register() { const [password, setPassword] = React.useState(""); const [confirmPassword, setConfirmPassword] = React.useState(""); + const [customerId, setCustomerId] = React.useState(null); + const [firstName, setFirstName] = React.useState(""); + const [lastName, setLastName] = React.useState(""); + + const [street, setStreet] = React.useState(""); + const [buildingNumber, setBuildingNumber] = React.useState(""); + const [apartmentNumber, setApartmentNumber] = React.useState(""); + const [city, setCity] = React.useState(""); + const [zipCode, setZipCode] = React.useState(""); + const [country, setCountry] = React.useState(""); + const termsCheckboxRef = React.useRef(null); const [error, setError] = React.useState(""); const [success, setSuccess] = React.useState(""); - const [registerLoading, setRegisterLoading] = React.useState(false); + const [registerFinished, setRegisterFinished] = React.useState(false); - const onSubmit = (e: any) => { + const [completionError, setCompletionError] = React.useState(""); + const [completionSuccess, setCompletionSuccess] = React.useState(""); + const [completionLoading, setCompletionLoading] = React.useState(false); + + const onRegister = async (e: any) => { e.preventDefault(); if (registerLoading) return; setError(""); @@ -47,22 +62,63 @@ export default function Register() { return; } - register(username, password, email) + register(email, password, "user") .then((res) => { + setCustomerId(res); setSuccess( - res?.data?.message || "Registration successful! Please login." + "Registration successful! Please complete data referring to you below." + ); + }) + .catch((err) => { + setError(err?.response?.data?.reason || "Something went wrong during registration!"); + }) + .finally(() => { + setRegisterLoading(false); + setRegisterFinished(true); + }); + }; + + const onComplete = async (e: any) => { + e.preventDefault(); + if (completionLoading) return; + setCompletionError(""); + setCompletionSuccess(""); + setCompletionLoading(true); + + completeCustomerRegistration( + customerId, + firstName, + lastName, + `${street}|${buildingNumber}|${apartmentNumber}|${city}|${zipCode}|${country}`, + "Empty") + .then((res) => { + setCompletionSuccess( + res?.data?.message || "Completion of registration successful! Please login." ); + + setEmail(""); setUsername(""); setPassword(""); setConfirmPassword(""); - setEmail(""); + + setCustomerId(null); + setFirstName(""); + setLastName(""); + + setStreet(""); + setBuildingNumber(""); + setApartmentNumber(""); + setCity(""); + setZipCode(""); + setCountry(""); + termsCheckboxRef.current!.checked = false; }) .catch((err) => { - setError(err?.response?.data?.message || "Something went wrong!"); + setCompletionError(err?.response?.data?.message || "Something went wrong during completion of registration!"); }) .finally(() => { - setRegisterLoading(false); + setCompletionLoading(false); }); }; @@ -78,21 +134,7 @@ export default function Register() { Please register below. If you already have an account, please login using the button in the top right corner.

- {error ? ( - - - Error! {error} - - - ) : null} - {success ? ( - - - Success! {success} - - - ) : null} -
+
+
+
+
+
+ {registerLoading ? ( )} + + {error ? ( + + + Error! {error} + + + ) : null} + {success ? ( + + + Success! {success} + + + ) : null} + + {(registerFinished && error === "") ? ( +
+
+
+
+
+ setFirstName(e.target.value)} + /> +
+ +
+
+
+ setLastName(e.target.value)} + /> +
+ +
+
+
+
+ setStreet(e.target.value)} + /> +
+ +
+
+
+ setBuildingNumber(e.target.value)} + /> +
+ +
+
+
+ setApartmentNumber(e.target.value)} + /> +
+ +
+
+
+ setCity(e.target.value)} + /> +
+ +
+
+
+ setZipCode(e.target.value)} + /> +
+ +
+
+
+ setCountry(e.target.value)} + /> +
+
+
+ + {(completionLoading) ? ( + + ) : ( + + )} +
+ ) : null} + + {completionError ? ( + + + Error! {completionError} + + + ) : null} + {completionSuccess ? ( + + + Success! {completionSuccess} + + + ) : null}
diff --git a/SwiftParcel.Web/frontend/src/utils/api.tsx b/SwiftParcel.Web/frontend/src/utils/api.tsx index eee97db..a1023e5 100644 --- a/SwiftParcel.Web/frontend/src/utils/api.tsx +++ b/SwiftParcel.Web/frontend/src/utils/api.tsx @@ -1,38 +1,37 @@ import axios from "axios"; -import { getUserInfo, saveUserInfo } from "./storage"; - - -const API_BASE_URL = 'http://localhost:6001'; +import { getUserIdFromStorage, getUserInfo, saveUserInfo } from "./storage"; const api = axios.create({ - baseURL: "http://localhost:5292", + baseURL: "http://api-gateway:5000", withCredentials: true, }); -api.interceptors.response.use( - response => response, - error => { - if (axios.isAxiosError(error) && error.response?.status === 401) { - //// saveUserInfo(null); - window.location.href = '/login'; - } - return Promise.reject(error); - } -); +// api.interceptors.response.use( +// response => response, +// error => { +// if (axios.isAxiosError(error) && error.response?.status === 401) { +// //// saveUserInfo(null); +// window.location.href = '/login'; +// } +// return Promise.reject(error); +// } +// ); -// Helper function to get the authorization header const getAuthHeader = () => { - const token = getUserInfo()?.token; - if (!token) throw new Error('No token found'); - return { Authorization: `Bearer ${token}` }; + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Not redirecting to login.'); + //window.location.href = '/login'; + return null; + } + console.log({Authorization: `Bearer ${userInfo.accessToken}`}) + return { Authorization: `Bearer ${userInfo.accessToken}` }; }; const defaultPageLimit = 10; export const login = async (email: string, password: string) => { - // const response = await api.post('/sign-in', { email, password }); - // return response.data; try { const response = await api.post('/identity/sign-in', { email, password }); const { accessToken, refreshToken, role, expires } = response.data; @@ -50,20 +49,77 @@ export const login = async (email: string, password: string) => { }; export const register = async ( - username: string, + email: string, password: string, - email: string + role: string ) => { try { - const response = await api.post(`/identity/sign-up`, { - username, - password, - email, - }); + + const payload = { + Email: email, + Password: password, + Role: role + }; + + console.log("Request payload (register):", payload); + + console.log("JSON being sent (register):", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/identity/sign-up`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Content-Type': 'application/json' + } + }) + + console.log("response:", response); + return response.data; + + } catch (registerError) { + if (axios.isAxiosError(registerError) && registerError.response) { + console.error('Error status:', registerError.response.status); + console.error('Error data:', registerError.response.data); + console.error('Error during registration:', registerError.message); + } else { + console.error('Error during registration:', registerError); + } + throw registerError; + } +}; + +export const completeCustomerRegistration = async ( + customerId: string, + firstName: string, + lastName: string, + address: string, + sourceAddress: string +) => { + try { + + const payload = { + CustomerId: customerId, + FirstName: firstName, + LastName: lastName, + Address: address, + SourceAddress: sourceAddress + }; + + console.log("Request payload (customer completion):", payload); + + console.log("JSON being sent (customer completion):", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/customers`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Content-Type': 'application/json' + } + }) + return response.data; + } catch (error) { - if (axios.isAxiosError(error)) { - console.error('Error during registration (Axios error):', error.response?.data || error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('Error status:', error.response.status); + console.error('Error data:', error.response.data); + console.error('Error during registration:', error.message); } else { console.error('Error during registration:', error); } @@ -84,50 +140,76 @@ export const logout = async () => { }; export const getProfile = async () => { - // const token = getUserInfo()?.token; - // if (!token) return Promise.reject(); - - // const res = await api.get("/auth/me", { - // headers: { - // Authorization: `Bearer ${token}`, - // }, - // }); - - // if (res.status === 401) { - // // saveUserInfo(null); - // return Promise.reject(); - // } + try { + const headers = getAuthHeader(); + const userId = getUserIdFromStorage(); + if (!userId) { + throw new Error('User ID not found'); + } - // return res; - const response = await api.get("/me", { headers: getAuthHeader() }); - return response.data; + const response = await api.get(`identity/users/${userId}`, { headers }); + return response.data; + } catch (error) { + if (axios.isAxiosError(error)) { + console.error('Error fetching profile:', error.response?.data || error.message); + if (error.response?.status === 401) { + saveUserInfo(null); + window.location.href = '/login'; + } + } else { + console.error('Error fetching profile:', error); + } + throw error; + } }; -export const getUsers = async ( - page: number, - perPage: number = defaultPageLimit, - extra = "" -) => { - - // in the case for the test reason: using the backend in the node - // const token = getUserInfo()?.token; - // const res = await api.get(`/users?page=${page}&size=${perPage}${extra}`, { - // headers: { - // Authorization: `Bearer ${token}`, - // }, - // }); - - const response = await api.get(`/users?page=${page}&size=${perPage}`, { headers: getAuthHeader() }); - if (response.status === 401) { - //// saveUserInfo(null); - return Promise.reject(); +export const getUsers = async (page = 1, perPage = defaultPageLimit) => { + try { + const response = await api.get(`/identity/users?page=${page}&size=${perPage}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error during getting users:', error); + throw error; } - - return response; }; +// This requewst is going OK +// the next reques is used for the testing purpose: +// ──(kaliuser㉿kali)-[~/…/wisdom_source.com/courier_app] +// └─$ curl -X POST 'http://localhost:5007/parcels' \ +// -H 'Content-Type: application/json' \ +// -d '{ +// "ParcelId": "00000000-0000-0000-0000-000000000000", +// "CustomerId": "00000000-0000-0000-0000-000000000000", +// "Description": "TestYYYYYYYY", +// "Width": 0.05, +// "Height": 0.05, +// "Depth": 0.05, +// "Weight": 0.5, +// "SourceStreet": "Plac politechniki", +// "SourceBuildingNumber": "1", +// "SourceApartmentNumber": "", +// "SourceCity": "Warszawa", +// "SourceZipCode": "00-420", +// "SourceCountry": "Polska", +// "DestinationStreet": "Koszykowa", +// "DestinationBuildingNumber": "21", +// "DestinationApartmentNumber": "37", +// "DestinationCity": "Warszawa", +// "DestinationZipCode": "00-420", +// "DestinationCountry": "Polska", +// "Priority": "Low", +// "AtWeekend": true, +// "PickupDate": "2023-12-22T00:00:00.000Z", +// "DeliveryDate": "2023-12-29T00:00:00.000Z", +// "IsCompany": false, +// "VipPackage": false +// }' + + export const createInquiry = async ( + customerId: string, description: string, width: number, height: number, @@ -153,370 +235,666 @@ export const createInquiry = async ( vipPackage: boolean ) => { try { - const response = await api.post(`/parcels`, { - description, - width, - height, - depth, - weight, - sourceStreet, - sourceBuildingNumber, - sourceApartmentNumber, - sourceCity, - sourceZipCode, - sourceCountry, - destinationStreet, - destinationBuildingNumber, - destinationApartmentNumber, - destinationCity, - destinationZipCode, - destinationCountry, - priority, - atWeekend, - pickupDate, - deliveryDate, - isCompany, - vipPackage - }); + + // Log the token for debugging + //console.log("Using access token:", userInfo.accessToken); + + //const userData = await getProfile(); + //const customerId = userData.id.toString() || "00000000-0000-0000-0000-000000000000"; + + const payload = { + ...(customerId !== null && { CustomerId: customerId }), + Description: description, + Width: width, + Height: height, + Depth: depth, + Weight: weight, + SourceStreet: sourceStreet, + SourceBuildingNumber: sourceBuildingNumber, + SourceApartmentNumber: sourceApartmentNumber, + SourceCity: sourceCity, + SourceZipCode: sourceZipCode, + SourceCountry: sourceCountry, + DestinationStreet: destinationStreet, + DestinationBuildingNumber: destinationBuildingNumber, + DestinationApartmentNumber: destinationApartmentNumber, + DestinationCity: destinationCity, + DestinationZipCode: destinationZipCode, + DestinationCountry: destinationCountry, + Priority: priority, + AtWeekend: atWeekend, + PickupDate: pickupDate, + DeliveryDate: deliveryDate, + IsCompany: isCompany, + VipPackage: vipPackage + }; + + console.log("Request payload:", payload); + + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/parcels`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Content-Type': 'application/json' + } + }) + // .then((inquiryResponse) => { + // console.log("inquiryResponse:", inquiryResponse); + // return inquiryResponse; + // }); + return response.data; - } catch (error) { - if (axios.isAxiosError(error)) { - console.error('Error during inquiry creation (Axios error):', error.response?.data || error.message); + + // return response.data; + + // try { + // const offerResponse = await new Promise((resolve, reject) => { + // setTimeout(() => { + // api.get(`deliveries-service/deliveries`, { params: { parcelId: inquiryResponse.data.parcelId } }) + // .then(resolve) + // .catch(reject); + // }, 30000); // Wait for 30 seconds + // }); + + // return { inquiry: inquiryResponse, offers: offerResponse }; + // } catch (offerError) { + // console.error('Error fetching courier offers:', offerError); + // // Return the inquiry response even if fetching offers fails + // return { inquiry: inquiryResponse.data, offers: null }; + // } + + } catch (inquiryError) { + if (axios.isAxiosError(inquiryError) && inquiryError.response) { + console.error('Error status:', inquiryError.response.status); + console.error('Error data:', inquiryError.response.data); + console.error('Error during inquiry creation:', inquiryError.message); } else { - console.error('Error during inquiry creation:', error); + console.error('Error during inquiry creation:', inquiryError); } - throw error; + throw inquiryError; } }; -export const getInquiries = async () => { +export const createOrder = async ( + customerId: string, + parcelId: string, + name: string, + email: string, + street: string, + buildingNumber: string, + apartmentNumber: string, + city: string, + zipCode: string, + country: string, + company: string +) => { try { - const response = await api.get(`/parcels`); + + const payload = { + ...(customerId !== null && { CustomerId: customerId }), + ParcelId: parcelId, + Name: name, + Email: email, + Address: { + street, + buildingNumber, + apartmentNumber, + city, + zipCode, + country + }, + Company: company + }; + + console.log("Request payload:", payload); + + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/orders`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Content-Type': 'application/json' + } + }) + return response.data; + + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during order creation:', orderError.message); + } else { + console.error('Error during order creation:', orderError); + } + throw orderError; + } +}; + +export const getInquiriesUser = async (customerId: string) => { + try { + const response = await api.get(`/parcels/customerId=${customerId}`, { headers: getAuthHeader() }); + return response; } catch (error) { console.error('Error during getting inquiries:', error); throw error; } }; -export const createCar = async (data: any) => { - const token = getUserInfo()?.token; - const res = await api.post(`/cars`, data, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const getInquiriesOfficeWorker = async () => { + try { + const response = await api.get(`/parcels/office-worker`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting inquiries:', error); + throw error; + } +}; - if (res.status === 401) { - // // saveUserInfo(null); - return Promise.reject(); +export const getOrder = async (orderId: string) => { + try { + const response = await api.get(`/orders/${orderId}`); + return response; + } catch (error) { + console.error('Error during getting orders:', error); + throw error; } +}; - return res; +export const getOrdersUser = async (customerId: string) => { + try { + const response = await api.get(`/orders/customerId=${customerId}`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting orders:', error); + throw error; + } }; -export const updateCar = async (carId: number, data: any) => { - const token = getUserInfo()?.token; - const res = await api.put(`/cars/${carId}`, data, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const getOffers = async (parcelId: string) => { + try { + const response = await api.get(`/parcels/${parcelId}/offers`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting offers:', error); + throw error; + } +}; - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); +export const getOfferRequests = async () => { + try { + const response = await api.get(`/orders/office-worker`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting offer requests:', error); + throw error; } +}; - return res; +export const getPendingOffers = async () => { + try { + const response = await api.get(`/orders/office-worker/pending`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting pending offers:', error); + throw error; + } }; -export const deleteCar = async (carId: number) => { - const token = getUserInfo()?.token; - const res = await api.delete(`/cars/${carId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const approvePendingOffer = async (orderId : string) => { + try { + const payload = { + OrderId: orderId + }; + + console.log("Request payload:", payload); - if (res.status === 401) { - //// saveUserInfo(null); - return Promise.reject(); - } + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); - return res; + const response = await api.put(`/orders/${orderId}/office-worker/approve`, JSON.parse(JSON.stringify(payload)), { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during approving pending offer:', error); + throw error; + } }; -export const getCar = async (carId: number) => { - const token = getUserInfo()?.token; - const res = await api.get(`/cars/${carId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const cancelPendingOffer = async (orderId : string, reason: string) => { + try { + const payload = { + OrderId: orderId, + Reason: reason + }; - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + console.log("Request payload:", payload); - return res; + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); + + const response = await api.put(`/orders/${orderId}/office-worker/cancel`, payload, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during cancelling pending offer:', error); + throw error; + } }; -export const getCarPersonal = async () => { - const token = getUserInfo()?.token; - const carId = getUserInfo()?.courier?.carId; +export const confirmOrder = async ( + orderId: string, + company: string +) => { + try { - if (!carId || !token) return Promise.reject(); + const payload = { + OrderId: orderId, + Company: company + }; - const res = await api.get(`/cars/${carId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + console.log("Request payload:", payload); - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); - return res; + const response = await api.post(`/orders/${orderId}/confirm`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Content-Type': 'application/json' + } + }) + + return response.data; + + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during order confirmation:', orderError.message); + } else { + console.error('Error during order confirmation:', orderError); + } + throw orderError; + } }; -export const getCars = async ( - page: number, - perPage: number = defaultPageLimit, - extra = "" +export const cancelOrder = async ( + orderId: string, + company: string ) => { - const token = getUserInfo()?.token; - const res = await api.get(`/cars?page=${page}&size=${perPage}${extra}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + try { - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + const payload = { + OrderId: orderId, + Company: company + }; - return res; -}; + console.log("Request payload:", payload); -export const createParcel = async (data: any) => { - const token = getUserInfo()?.token; - const res = await api.post(`/parcels`, data, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + const response = await api.delete(`/orders/${orderId}/cancel`, JSON.parse(JSON.stringify(payload))) + + return response.data; - return res; + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during order cancellation:', orderError.message); + } else { + console.error('Error during order cancellation:', orderError); + } + throw orderError; + } }; -export const updateParcel = async (parcelId: string, data: any) => { - const token = getUserInfo()?.token; - const res = await api.put(`/parcels/${parcelId}`, data, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const addCustomerToOrder = async ( + orderId: string, + customerId: string +) => { + try { - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Redirecting to login.'); + window.location.href = '/login'; + return; + } - return res; -}; + const payload = { + OrderId: orderId, + CustomerId: customerId + }; -export const deleteParcel = async (parcelId: string) => { - const token = getUserInfo()?.token; - const res = await api.delete(`/parcels/${parcelId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + console.log("Request payload:", payload); - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); - return res; -}; + const response = await api.post(`/orders/${orderId}/customer`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Authorization': `${userInfo.accessToken}`, + 'Content-Type': 'application/json' + } + }) -export const getParcel = async (parcelId: string) => { - const token = getUserInfo()?.token; - const res = await api.get(`/parcels/${parcelId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + return response.data; - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during adding customer to order:', orderError.message); + } else { + console.error('Error during adding customer to order:', orderError); + } + throw orderError; } - - return res; }; -export const getParcels = async ( - page: number, - perPage: number = defaultPageLimit, - extra = "" -) => { - const token = getUserInfo()?.token; - const res = await api.get(`/parcels?page=${page}&size=${perPage}${extra}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); +export const getYourDeliveries = async (courierId: string) => { + try { + const response = await api.get(`/deliveries/courierId=${courierId}`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting your deliveries:', error); + throw error; } +}; - return res; +export const getPendingDeliveries = async () => { + try { + const response = await api.get(`/deliveries/pending`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting pending deliveries:', error); + throw error; + } }; -export const getParcelsForCourier = async ( - courierId: number, - page: number, - perPage: number = defaultPageLimit +export const assignCourierToDelivery = async ( + deliveryId: string, + courierId: string ) => { - const token = getUserInfo()?.token; - const res = await api.get( - `/couriers/${courierId}/parcels?page=${page}&size=${perPage}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, + try { + + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Redirecting to login.'); + window.location.href = '/login'; + return; } - ); + + const payload = { + DeliveryId: deliveryId, + CourierId: courierId + }; + + console.log("Request payload:", payload); - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/deliveries/${deliveryId}/courier`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Authorization': `${userInfo.accessToken}`, + 'Content-Type': 'application/json' + } + }) + + return response.data; + + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during assigning delivery to courier:', orderError.message); + } else { + console.error('Error during assigning delivery to courier:', orderError); + } + throw orderError; } +} + +export const pickupDelivery = async ( + deliveryId: string +) => { + try { - return res; + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Redirecting to login.'); + window.location.href = '/login'; + return; + } + + const payload = { + DeliveryId: deliveryId + }; + + console.log("Request payload:", payload); + + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/deliveries/${deliveryId}/pick-up`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Authorization': `${userInfo.accessToken}`, + 'Content-Type': 'application/json' + } + }) + + return response.data; + + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during picking up delivery:', orderError.message); + } else { + console.error('Error during picking up delivery:', orderError); + } + throw orderError; + } }; -export const getParcelsForCar = async ( - carId: number, - page: number, - perPage: number = defaultPageLimit +export const completeDelivery = async ( + deliveryId: string, + deliveryAttemptDate: any ) => { - const token = getUserInfo()?.token; - const res = await api.get( - `/cars/${carId}/parcels?page=${page}&size=${perPage}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, + try { + + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Redirecting to login.'); + window.location.href = '/login'; + return; } - ); - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + const payload = { + DeliveryId: deliveryId, + DeliveryAttemptDate: deliveryAttemptDate + }; - return res; -}; + console.log("Request payload:", payload); -export const createCourier = async (data: any) => { - const token = getUserInfo()?.token; - const res = await api.post(`/couriers`, data, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + const response = await api.post(`/deliveries/${deliveryId}/complete`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Authorization': `${userInfo.accessToken}`, + 'Content-Type': 'application/json' + } + }) + + return response.data; - return res; + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during completing delivery:', orderError.message); + } else { + console.error('Error during completing delivery:', orderError); + } + throw orderError; + } }; -export const updateCourier = async (courierId: number, data: any) => { - const token = getUserInfo()?.token; - const res = await api.put(`/couriers/${courierId}`, data, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const failDelivery = async ( + deliveryId: string, + deliveryAttemptDate: any, + reason: string +) => { + try { - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); - } + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Redirecting to login.'); + window.location.href = '/login'; + return; + } - return res; + const payload = { + DeliveryId: deliveryId, + DeliveryAttemptDate: deliveryAttemptDate, + Reason: reason + }; + + console.log("Request payload:", payload); + + console.log("JSON being sent:", JSON.parse(JSON.stringify(payload))); + + const response = await api.post(`/deliveries/${deliveryId}/fail`, JSON.parse(JSON.stringify(payload)), { + headers: { + 'Authorization': `${userInfo.accessToken}`, + 'Content-Type': 'application/json' + } + }) + + return response.data; + + } catch (orderError) { + if (axios.isAxiosError(orderError) && orderError.response) { + console.error('Error status:', orderError.response.status); + console.error('Error data:', orderError.response.data); + console.error('Error during failing delivery:', orderError.message); + } else { + console.error('Error during failing delivery:', orderError); + } + throw orderError; + } }; -export const deleteCourier = async (courierId: number) => { - const token = getUserInfo()?.token; - const res = await api.delete(`/couriers/${courierId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const getCustomerData = async (customerId: string) => { + try { + const response = await api.get(`/customers/${customerId}`, { headers: getAuthHeader() }); + return response; + } catch (error) { + console.error('Error during getting customer data:', error); + throw error; + } +}; - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); +export const updateParcel = async (parcelId: string, data: any) => { + try { + const response = await api.put(`/parcels/${parcelId}`, data, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error updating parcel:', error); + throw error; } +}; - return res; +export const deleteParcel = async (parcelId: string) => { + try { + const response = await api.delete(`/parcels/${parcelId}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error deleting parcel:', error); + throw error; + } }; -export const getCourier = async (courierId: number) => { - const token = getUserInfo()?.token; - const res = await api.get(`/couriers/${courierId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const getParcel = async (parcelId: string) => { + try { + const response = await api.get(`/parcels/${parcelId}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error fetching parcel:', error); + throw error; + } +}; - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); +export const getCouriers = async (page = 1, perPage = defaultPageLimit) => { + try { + const response = await api.get(`/couriers?page=${page}&size=${perPage}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error during getting couriers:', error); + throw error; } +}; - return res; +export const getCourier = async (courierId) => { + try { + const response = await api.get(`/couriers/${courierId}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error fetching courier:', error); + throw error; + } }; -export const getCouriers = async (page: number, perPage: number = 10) => { - const token = getUserInfo()?.token; - const res = await api.get(`/couriers?page=${page}&size=${perPage}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); +export const createCourier = async (courierData) => { + try { + const response = await api.post(`/couriers`, courierData, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error creating courier:', error); + throw error; + } +}; - if (res.status === 401) { - // saveUserInfo(null); - return Promise.reject(); +export const updateCourier = async (courierId, courierData) => { + try { + const response = await api.put(`/couriers/${courierId}`, courierData, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error updating courier:', error); + throw error; } +}; - return res; +export const deleteCourier = async (courierId) => { + try { + const response = await api.delete(`/couriers/${courierId}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error deleting courier:', error); + throw error; + } }; -export const getCoordinates = async (address: string) => { - const res = await axios.get( - `https://nominatim.openstreetmap.org/search?q=${address}&format=json` - ); +export const getDeliveries = async (deliveryId: string) => { + try { + const response = await api.get(`/deliveries/${deliveryId}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error fetching delivery:', error); + throw error; + } +}; - return res; +export const getDelivery = async (deliveryId) => { + try { + const response = await api.get(`/deliveries/${deliveryId}`, { headers: getAuthHeader() }); + return response.data; + } catch (error) { + console.error('Error fetching delivery:', error); + throw error; + } }; + +export const getCarPersonal = {}; +export const deleteCar = {}; +export const getParcels = {}; +export const createCar = {}; +export const updateCar = {}; +export const getCar = {}; +export const getCars = {}; +export const createParcel = {}; +export const getParcelsForCar = {}; +export const getParcelsForCourier = {}; \ No newline at end of file diff --git a/SwiftParcel.Web/frontend/src/utils/dist/api.js b/SwiftParcel.Web/frontend/src/utils/dist/api.js new file mode 100644 index 0000000..9db86f1 --- /dev/null +++ b/SwiftParcel.Web/frontend/src/utils/dist/api.js @@ -0,0 +1,495 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.getParcelsForCourier = exports.getParcelsForCar = exports.createParcel = exports.getCars = exports.getCar = exports.updateCar = exports.createCar = exports.getParcels = exports.deleteCar = exports.getCarPersonal = exports.getDelivery = exports.getDeliveries = exports.deleteCourier = exports.updateCourier = exports.createCourier = exports.getCourier = exports.getCouriers = exports.getParcel = exports.deleteParcel = exports.updateParcel = exports.getInquiries = exports.createInquiry = exports.getUsers = exports.getProfile = exports.logout = exports.register = exports.login = void 0; +var axios_1 = require("axios"); +var storage_1 = require("./storage"); +var API_BASE_URL = 'http://localhost:6001'; +var api = axios_1["default"].create({ + baseURL: "http://localhost:5292", + withCredentials: true +}); +// api.interceptors.response.use( +// response => response, +// error => { +// if (axios.isAxiosError(error) && error.response?.status === 401) { +// //// saveUserInfo(null); +// window.location.href = '/login'; +// } +// return Promise.reject(error); +// } +// ); +var getAuthHeader = function () { + var userInfo = storage_1.getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + console.warn('No user token found. Redirecting to login.'); + window.location.href = '/login'; + return null; + } + console.log({ Authorization: "Bearer " + userInfo.accessToken }); + return { Authorization: "Bearer " + userInfo.accessToken }; +}; +var defaultPageLimit = 10; +exports.login = function (email, password) { return __awaiter(void 0, void 0, void 0, function () { + var response, _a, accessToken, refreshToken, role, expires, error_1; + var _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + _c.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.post('/identity/sign-in', { email: email, password: password })]; + case 1: + response = _c.sent(); + _a = response.data, accessToken = _a.accessToken, refreshToken = _a.refreshToken, role = _a.role, expires = _a.expires; + storage_1.saveUserInfo({ token: accessToken, refreshToken: refreshToken, role: role, expires: expires }); + return [2 /*return*/, response.data]; + case 2: + error_1 = _c.sent(); + if (axios_1["default"].isAxiosError(error_1)) { + console.error('Error during login:', ((_b = error_1.response) === null || _b === void 0 ? void 0 : _b.data) || error_1.message); + } + else { + console.error('Error during login:', error_1); + } + throw error_1; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.register = function (username, password, email) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_2; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _b.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.post("/identity/sign-up", { + username: username, + password: password, + email: email + })]; + case 1: + response = _b.sent(); + return [2 /*return*/, response.data]; + case 2: + error_2 = _b.sent(); + if (axios_1["default"].isAxiosError(error_2)) { + console.error('Error during registration (Axios error):', ((_a = error_2.response) === null || _a === void 0 ? void 0 : _a.data) || error_2.message); + } + else { + console.error('Error during registration:', error_2); + } + throw error_2; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.logout = function () { return __awaiter(void 0, void 0, void 0, function () { + var headers, error_3; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, 3, 4]); + headers = getAuthHeader(); + return [4 /*yield*/, api.get('/identity/logout', { headers: headers })]; + case 1: + _a.sent(); + return [3 /*break*/, 4]; + case 2: + error_3 = _a.sent(); + console.error('Logout failed:', error_3); + return [3 /*break*/, 4]; + case 3: + storage_1.saveUserInfo(null); + window.location.href = '/login'; + return [7 /*endfinally*/]; + case 4: return [2 /*return*/]; + } + }); +}); }; +exports.getProfile = function () { return __awaiter(void 0, void 0, void 0, function () { + var headers, userId, response, error_4; + var _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + _c.trys.push([0, 2, , 3]); + headers = getAuthHeader(); + userId = storage_1.getUserIdFromStorage(); + if (!userId) { + throw new Error('User ID not found'); + } + return [4 /*yield*/, api.get("identity/users/" + userId, { headers: headers })]; + case 1: + response = _c.sent(); + console.log("identity/users/${userId}: ", response.data); + return [2 /*return*/, response.data]; + case 2: + error_4 = _c.sent(); + if (axios_1["default"].isAxiosError(error_4)) { + console.error('Error fetching profile:', ((_a = error_4.response) === null || _a === void 0 ? void 0 : _a.data) || error_4.message); + if (((_b = error_4.response) === null || _b === void 0 ? void 0 : _b.status) === 401) { + storage_1.saveUserInfo(null); + window.location.href = '/login'; + } + } + else { + console.error('Error fetching profile:', error_4); + } + throw error_4; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.getUsers = function (page, perPage) { + if (page === void 0) { page = 1; } + if (perPage === void 0) { perPage = defaultPageLimit; } + return __awaiter(void 0, void 0, void 0, function () { + var response, error_5; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/identity/users?page=" + page + "&size=" + perPage, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_5 = _a.sent(); + console.error('Error during getting users:', error_5); + throw error_5; + case 3: return [2 /*return*/]; + } + }); + }); +}; +exports.createInquiry = function (description, width, height, depth, weight, sourceStreet, sourceBuildingNumber, sourceApartmentNumber, sourceCity, sourceZipCode, sourceCountry, destinationStreet, destinationBuildingNumber, destinationApartmentNumber, destinationCity, destinationZipCode, destinationCountry, priority, atWeekend, pickupDate, deliveryDate, isCompany, vipPackage) { return __awaiter(void 0, void 0, void 0, function () { + var userInfo, userResponse, customerId, payload, inquiryResponse_1, offerResponse, offerError_1, inquiryError_1; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _b.trys.push([0, 7, , 8]); + userInfo = storage_1.getUserInfo(); + // if (!userInfo || !userInfo.accessToken) { + // console.warn('No user token found. Redirecting to login.'); + // window.location.href = '/login'; + // return; + // } + // Log the token for debugging + console.log("Using access token:", userInfo.accessToken); + return [4 /*yield*/, exports.getProfile()]; + case 1: + userResponse = _b.sent(); + customerId = userResponse ? userResponse.id : null; + payload = { + // ParcelId: "00000000-0000-0000-0000-000000000000", // Remove if backend generates ID + CustomerId: customerId, + Description: description, + Width: width, + Height: height, + Depth: depth, + Weight: weight, + SourceStreet: sourceStreet, + SourceBuildingNumber: sourceBuildingNumber, + SourceApartmentNumber: sourceApartmentNumber, + SourceCity: sourceCity, + SourceZipCode: sourceZipCode, + SourceCountry: sourceCountry, + DestinationStreet: destinationStreet, + DestinationBuildingNumber: destinationBuildingNumber, + DestinationApartmentNumber: destinationApartmentNumber, + DestinationCity: destinationCity, + DestinationZipCode: destinationZipCode, + DestinationCountry: destinationCountry, + Priority: priority, + AtWeekend: atWeekend, + PickupDate: pickupDate, + DeliveryDate: deliveryDate, + IsCompany: isCompany, + VipPackage: vipPackage + }; + console.log("Request payload:", payload); + return [4 /*yield*/, api.post("/parcels", payload, { + headers: { Authorization: "Bearer " + userInfo.accessToken } + })]; + case 2: + inquiryResponse_1 = _b.sent(); + _b.label = 3; + case 3: + _b.trys.push([3, 5, , 6]); + return [4 /*yield*/, new Promise(function (resolve, reject) { + setTimeout(function () { + api.get("deliveries-service/deliveries", { params: { parcelId: inquiryResponse_1.data.parcelId } }) + .then(resolve)["catch"](reject); + }, 30000); // Wait for 30 seconds + })]; + case 4: + offerResponse = _b.sent(); + return [2 /*return*/, { inquiry: inquiryResponse_1, offers: offerResponse }]; + case 5: + offerError_1 = _b.sent(); + console.error('Error fetching courier offers:', offerError_1); + // Return the inquiry response even if fetching offers fails + return [2 /*return*/, { inquiry: inquiryResponse_1.data, offers: null }]; + case 6: return [3 /*break*/, 8]; + case 7: + inquiryError_1 = _b.sent(); + if (axios_1["default"].isAxiosError(inquiryError_1)) { + console.error('Error during inquiry creation (Axios error):', ((_a = inquiryError_1.response) === null || _a === void 0 ? void 0 : _a.data) || inquiryError_1.message); + } + else { + console.error('Error during inquiry creation:', inquiryError_1); + } + throw inquiryError_1; + case 8: return [2 /*return*/]; + } + }); +}); }; +exports.getInquiries = function () { return __awaiter(void 0, void 0, void 0, function () { + var response, error_6; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/parcels", { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_6 = _a.sent(); + console.error('Error during getting inquiries:', error_6); + throw error_6; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.updateParcel = function (parcelId, data) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_7; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.put("/parcels/" + parcelId, data, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_7 = _a.sent(); + console.error('Error updating parcel:', error_7); + throw error_7; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.deleteParcel = function (parcelId) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_8; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api["delete"]("/parcels/" + parcelId, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_8 = _a.sent(); + console.error('Error deleting parcel:', error_8); + throw error_8; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.getParcel = function (parcelId) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_9; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/parcels/" + parcelId, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_9 = _a.sent(); + console.error('Error fetching parcel:', error_9); + throw error_9; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.getCouriers = function (page, perPage) { + if (page === void 0) { page = 1; } + if (perPage === void 0) { perPage = defaultPageLimit; } + return __awaiter(void 0, void 0, void 0, function () { + var response, error_10; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/couriers?page=" + page + "&size=" + perPage, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_10 = _a.sent(); + console.error('Error during getting couriers:', error_10); + throw error_10; + case 3: return [2 /*return*/]; + } + }); + }); +}; +exports.getCourier = function (courierId) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_11; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/couriers/" + courierId, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_11 = _a.sent(); + console.error('Error fetching courier:', error_11); + throw error_11; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.createCourier = function (courierData) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_12; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.post("/couriers", courierData, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_12 = _a.sent(); + console.error('Error creating courier:', error_12); + throw error_12; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.updateCourier = function (courierId, courierData) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_13; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.put("/couriers/" + courierId, courierData, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_13 = _a.sent(); + console.error('Error updating courier:', error_13); + throw error_13; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.deleteCourier = function (courierId) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_14; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api["delete"]("/couriers/" + courierId, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_14 = _a.sent(); + console.error('Error deleting courier:', error_14); + throw error_14; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.getDeliveries = function (deliveryId) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_15; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/deliveries/" + deliveryId, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_15 = _a.sent(); + console.error('Error fetching delivery:', error_15); + throw error_15; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.getDelivery = function (deliveryId) { return __awaiter(void 0, void 0, void 0, function () { + var response, error_16; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [4 /*yield*/, api.get("/deliveries/" + deliveryId, { headers: getAuthHeader() })]; + case 1: + response = _a.sent(); + return [2 /*return*/, response.data]; + case 2: + error_16 = _a.sent(); + console.error('Error fetching delivery:', error_16); + throw error_16; + case 3: return [2 /*return*/]; + } + }); +}); }; +exports.getCarPersonal = {}; +exports.deleteCar = {}; +exports.getParcels = {}; +exports.createCar = {}; +exports.updateCar = {}; +exports.getCar = {}; +exports.getCars = {}; +exports.createParcel = {}; +exports.getParcelsForCar = {}; +exports.getParcelsForCourier = {}; diff --git a/SwiftParcel.Web/frontend/src/utils/others.tsx b/SwiftParcel.Web/frontend/src/utils/others.tsx index c537352..ad7391c 100644 --- a/SwiftParcel.Web/frontend/src/utils/others.tsx +++ b/SwiftParcel.Web/frontend/src/utils/others.tsx @@ -2,19 +2,21 @@ import { Fragment, ReactNode } from "react"; import { getUserInfo } from "./storage"; import { Navigate } from "react-router-dom"; - - -export function RolesAuthRoute({children, role,}: { +export function RolesAuthRoute({children, roles,}: { children: ReactNode; - role: any; + roles: (string | null)[]; }) { const userInfo = getUserInfo(); - const allowed = - (role === null && userInfo === null) || - (role === "Courier" && userInfo?.courier != null) || - userInfo?.user?.role === role - ? true - : false; + console.log("UserInfo: ", userInfo) + + console.log("Required role: ", roles); + console.log("User's role: ", userInfo?.role); + + const allowed = roles.includes(userInfo?.role) || (roles.includes(null) && !userInfo); + + if (allowed) { + return {children}; + } if (allowed) { return {children}; diff --git a/SwiftParcel.Web/frontend/src/utils/storage.tsx b/SwiftParcel.Web/frontend/src/utils/storage.tsx index d8ce34c..475b24e 100644 --- a/SwiftParcel.Web/frontend/src/utils/storage.tsx +++ b/SwiftParcel.Web/frontend/src/utils/storage.tsx @@ -7,4 +7,51 @@ export const saveUserInfo = (userInfo: any) => { export const getUserInfo = () => { const userInfo = localStorage.getItem("userInfo"); return userInfo ? JSON.parse(userInfo) : null; -}; \ No newline at end of file +}; + +export const getUserIdFromStorage = () => { + const userInfo = getUserInfo(); + if (!userInfo || !userInfo.accessToken) { + return null; + } + + const tokenParts = userInfo.accessToken.split('.'); + if (tokenParts.length !== 3) { + return null; + } + + try { + const payload = JSON.parse(decodeBase64Url(tokenParts[1])); + let userId = payload.sub; + if (!userId) { + return null; + } + userId = formatGuid(userId); + return userId; + } catch (error) { + console.error('Error decoding JWT:', error); + return null; + } +}; + +function formatGuid(userId) { + if (!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(userId)) { + userId = userId.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5"); + } + return userId; +} + +function decodeBase64Url(str) { + str = str.replace(/-/g, '+').replace(/_/g, '/'); + + const pad = str.length % 4; + if (pad) { + if (pad === 1) { + throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding'); + } + str += new Array(5 - pad).join('='); + } + + return atob(str); +} + diff --git a/SwiftParcel.Web/frontend/src/utils/userPopulation.ts b/SwiftParcel.Web/frontend/src/utils/userPopulation.ts deleted file mode 100644 index de7047c..0000000 --- a/SwiftParcel.Web/frontend/src/utils/userPopulation.ts +++ /dev/null @@ -1,41 +0,0 @@ -// import mongoose from 'mongoose'; -// import bcrypt from 'bcrypt'; - -// const userSchema = new mongoose.Schema({ -// _id: mongoose.Schema.Types.ObjectId, -// email: String, -// role: String, -// password: String, -// createdAt: Date, -// permissions: [String], -// }); - -// const User = mongoose.model('User', userSchema); - -// export const populateAdminUser = async () => { -// try { -// await mongoose.connect(process.env.CONNECTION_STRING || ''); - -// const existingAdmin = await User.findOne({ role: 'admin' }).exec(); -// if (existingAdmin) { -// console.log('Admin user already exists.'); -// return; -// } - -// const hashedPassword = await bcrypt.hash('superadmin', 10); - -// const adminUser = new User({ -// _id: new mongoose.Types.ObjectId(), -// email: 'admin@email.com', -// role: 'admin', -// password: hashedPassword, -// createdAt: new Date(), -// permissions: [], // Add necessary permissions here -// }); - -// await adminUser.save(); -// console.log('Admin user created successfully.'); -// } catch (error) { -// console.error('Error creating admin user:', error); -// } -// }; diff --git a/SwiftParcel.Web/nginx.conf b/SwiftParcel.Web/nginx.conf new file mode 100644 index 0000000..f2037da --- /dev/null +++ b/SwiftParcel.Web/nginx.conf @@ -0,0 +1,25 @@ +events { + worker_connections 1024; +} + +http { + server { + listen 80; + server_name 64.23.128.163; + + # Location of your React app's static files + root /usr/share/nginx/html; + + location /SaintAngeLs/courier_app/ { + alias /usr/share/nginx/html/; + try_files $uri $uri/ /SaintAngeLs/courier_app/index.html; + } + + # location /SaintAngeLs/courier_app/static/ { + # alias /usr/share/nginx/html/static/; + # try_files $uri $uri/ /SaintAngeLs/courier_app/static; + # } + + error_page 404 /index.html; + } +} diff --git a/SwiftParcel.Web/scripts/dockerize.sh b/SwiftParcel.Web/scripts/dockerize.sh new file mode 100755 index 0000000..88d7ddc --- /dev/null +++ b/SwiftParcel.Web/scripts/dockerize.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Define container and image names +CONTAINER_NAME="swiftparcel-web" +IMAGE_NAME="swift-parcel-web" +TAG="latest" +REGISTRY="adrianvsaint" + +# Stop and remove the existing container if it exists +echo "- [ ] Stopping and removing existing container if it exists... ... ... ... ... ... ... ... ... ..." +docker stop $CONTAINER_NAME || true +docker rm $CONTAINER_NAME || true + +# Navigate to the appropriate directory +cd ../ + +# Build the Docker image +echo "- [ ] Building Docker image... ... ... ... ... ... ... ... ... ..." +docker build -t $IMAGE_NAME:$TAG . --no-cache + +# Tag the image for the registry +echo "- [ ] Tagging Docker image... ... ... ... ... ... ... ... ... ..." +docker tag $IMAGE_NAME:$TAG $REGISTRY/$IMAGE_NAME:$TAG + +# Push the image to the registry +echo "- [ ] Pushing Docker image to registry... ... ... ... ... ... ... ... ... ..." +docker push $REGISTRY/$IMAGE_NAME:$TAG + +# Recreate the container +echo "- [ ] Recreating the container... ... ... ... ... ... ... ... ... ..." +docker run -d --name $CONTAINER_NAME -p 3001:80 $REGISTRY/$IMAGE_NAME:$TAG + +echo "Container recreated successfully." diff --git a/SwiftParcel/d-docker-compose/deployment.yml b/SwiftParcel/d-docker-compose/deployment.yml new file mode 100644 index 0000000..fdae03d --- /dev/null +++ b/SwiftParcel/d-docker-compose/deployment.yml @@ -0,0 +1,225 @@ + version: "3.7" + + services: + # Microservices + identity-service: + image: adrianvsaint/swift-parcel-identity-service:latest + container_name: identity-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5004:80 + networks: + - swiftparcel + + web: + image: adrianvsaint/swift-parcel-web:latest + container_name: swiftparcel-web + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 3001:80 + networks: + - swiftparcel + + deliveries-service: + image: adrianvsaint/swift-parcel-deliveries-service:latest + container_name: deliveries-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5003:80 + networks: + - swiftparcel + + parcels-service: + image: adrianvsaint/swift-parcel-parcels-service:latest + container_name: parcels-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5007:80 + networks: + - swiftparcel + + pricing-service: + image: adrianvsaint/swift-parcel-pricing-service:latest + container_name: pricing-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5008:80 + networks: + - swiftparcel + + customers-service: + image: adrianvsaint/swift-parcel-customers-service:latest + container_name: customers-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5002:80 + networks: + - swiftparcel + + orders-service: + image: adrianvsaint/swift-parcel-orders-service:latest + container_name: orders-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5006:80 + networks: + - swiftparcel + + lecturer-api-service: + image: adrianvsaint/swift-parcel-external-api-lecturer-service:latest + container_name: lecturer-api-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5005:80 + networks: + - swiftparcel + + api-gateway: + image: adrianvsaint/swift-parcel-apigateway-service:latest + container_name: api-gateway + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + environment: + - NTRADA_CONFIG=ntrada.yml + ports: + - 5000:80 + networks: + - swiftparcel + + # Microinfrastructure + consul: + image: consul:1.9.5 + container_name: consul + restart: unless-stopped + networks: + - swiftparcel + ports: + - 8500:8500 + + fabio: + image: fabiolb/fabio + container_name: fabio + restart: unless-stopped + environment: + - FABIO_REGISTRY_CONSUL_ADDR=consul:8500 + networks: + - swiftparcel + ports: + - 9998:9998 + - 9999:9999 + + grafana: + image: grafana/grafana + container_name: grafana + restart: unless-stopped + networks: + - swiftparcel + ports: + - 3000:3000 + + jaeger: + image: jaegertracing/all-in-one + container_name: jaeger + restart: unless-stopped + networks: + - swiftparcel + ports: + - 5775:5775/udp + - 5778:5778 + - 6831:6831/udp + - 6832:6832/udp + - 9411:9411 + - 14268:14268 + - 16686:16686 + + rabbitmq: + build: ./rabbitmq + container_name: rabbitmq + restart: unless-stopped + networks: + - swiftparcel + ports: + - 5672:5672 + - 15672:15672 + - 15692:15692 + - 25672:25672 + - 35672:35672 + - 45672:45672 + - 55672:55672 + # volumes: + # - rabbitmq:/var/lib/rabbitmq + + prometheus: + image: prom/prometheus + container_name: prometheus + restart: unless-stopped + networks: + - swiftparcel + ports: + - 9090:9090 + volumes: + - prometheus-config:/etc/prometheus + + redis: + image: redis + container_name: redis + restart: unless-stopped + networks: + - swiftparcel + ports: + - 6379:6379 + volumes: + - redis:/data + + seq: + image: datalust/seq + container_name: seq + restart: unless-stopped + environment: + - ACCEPT_EULA=Y + networks: + - swiftparcel + ports: + - 5341:80 + + vault: + image: vault:1.9.0 + container_name: vault + restart: unless-stopped + environment: + - VAULT_ADDR=http://127.0.0.1:8200 + - VAULT_DEV_ROOT_TOKEN_ID=secret + cap_add: + - IPC_LOCK + networks: + - swiftparcel + ports: + - 8200:8200 + + networks: + swiftparcel: + name: swiftparcel-network + + volumes: + mongo: + driver: local + redis: + driver: local + prometheus-config: {} diff --git a/SwiftParcel/d-docker-compose/dockerize_all.sh b/SwiftParcel/d-docker-compose/dockerize_all.sh new file mode 100755 index 0000000..0a9de7d --- /dev/null +++ b/SwiftParcel/d-docker-compose/dockerize_all.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +directories=( + "SwiftParcel.API.Gateway" + "SwiftParcel.ExternalAPI.Lecturer" + "SwiftParcel.Services.Customers" + "SwiftParcel.Services.Deliveries" + "SwiftParcel.Services.Orders" + "SwiftParcel.Services.Parcels" + "SwiftParcel.Services.Pricing" + "SwiftParcel.Web" + "SwifttParcel.Services.Identity" +) + +for dir in "${directories[@]}"; do + echo "Processing $dir..." + + if [ -f "$dir/scripts/dockerize.sh" ]; then + echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " + (cd "$dir/scripts" && ./dockerize.sh) + echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " + else + echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " + echo "dockerize.sh not found in $dir" + echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " + fi +done diff --git a/SwiftParcel/d-docker-compose/micro-infrastructure.yml b/SwiftParcel/d-docker-compose/micro-infrastructure.yml index 640c606..dee6e21 100644 --- a/SwiftParcel/d-docker-compose/micro-infrastructure.yml +++ b/SwiftParcel/d-docker-compose/micro-infrastructure.yml @@ -1,4 +1,4 @@ -version: "1.0" +version: "3.7" services: diff --git a/SwiftParcel/d-docker-compose/micro-services-local.yml b/SwiftParcel/d-docker-compose/micro-services-local.yml index a39e6f0..2a8557e 100644 --- a/SwiftParcel/d-docker-compose/micro-services-local.yml +++ b/SwiftParcel/d-docker-compose/micro-services-local.yml @@ -1,4 +1,4 @@ -version: "2.1" +version: "3.7" services: diff --git a/SwiftParcel/d-docker-compose/prometeus/Dockerfile b/SwiftParcel/d-docker-compose/prometheus/Dockerfile similarity index 100% rename from SwiftParcel/d-docker-compose/prometeus/Dockerfile rename to SwiftParcel/d-docker-compose/prometheus/Dockerfile diff --git a/SwiftParcel/d-docker-compose/prometeus/prometeus.yml b/SwiftParcel/d-docker-compose/prometheus/prometheus.yml similarity index 90% rename from SwiftParcel/d-docker-compose/prometeus/prometeus.yml rename to SwiftParcel/d-docker-compose/prometheus/prometheus.yml index 9220806..91ca0f1 100644 --- a/SwiftParcel/d-docker-compose/prometeus/prometeus.yml +++ b/SwiftParcel/d-docker-compose/prometheus/prometheus.yml @@ -27,10 +27,6 @@ scrape_configs: static_configs: - targets: ['orders-service'] - # - job_name: 'ordermaker-service' - # static_configs: - # - targets: ['ordermaker-service'] - - job_name: 'parcels-service' static_configs: - targets: ['parcels-service'] @@ -38,6 +34,10 @@ scrape_configs: - job_name: 'pricing-service' static_configs: - targets: ['pricing-service'] + + - job_name: 'lecturer-api-service' + static_configs: + - targets: ['lecturer-api-service'] - job_name: 'rabbitmq' static_configs: diff --git a/SwiftParcel/d-docker-compose/services.yml b/SwiftParcel/d-docker-compose/services.yml index 1fade58..d5d2597 100644 --- a/SwiftParcel/d-docker-compose/services.yml +++ b/SwiftParcel/d-docker-compose/services.yml @@ -1,27 +1,23 @@ -version: "1.0" +version: "3.7" services: identity-service: - image: apa/swiftparcel.services.identity + image: adrianvsaint/swift-parcel-identity-service container_name: identity-service + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - - 5292:80 + - 5005:80 networks: - swiftparcel - availability-service: - image: apa/swiftparcel.services.availability - container_name: availability-service - restart: unless-stopped - ports: - - 5001:80 - networks: - - swiftparcel web: - image: apa/swiftparcel.web + image: adrianvsaint/swift-parcel-web container_name: swiftparcel-web + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - 3001:80 @@ -29,8 +25,10 @@ services: - swiftparcel deliveries-service: - image: apa/swiftparcel.services.deliveries + image: adrianvsaint/swift-parcel-deliveries-service container_name: deliveries-service + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - 5003:80 @@ -38,8 +36,10 @@ services: - swiftparcel parcels-service: - image: apa/swiftparcel.services.parcels + image: adrianvsaint/swift-parcel-parcels-service container_name: parcels-service + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - 5007:80 @@ -47,8 +47,10 @@ services: - swiftparcel pricing-service: - image: apa/swiftparcel.services.pricing + image: adrianvsaint/swift-parcel-pricing-service container_name: pricing-service + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - 5008:80 @@ -56,8 +58,10 @@ services: - swiftparcel customers-service: - image: apa/swiftparcel.services.customers + image: adrianvsaint/swift-parcel-customers-service container_name: customers-service + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - 5002:80 @@ -65,14 +69,40 @@ services: - swiftparcel orders-service: - image: apa/pacco.services.orders + image: adrianvsaint/swift-parcel-orders-service container_name: orders-service + environment: + - ASPNETCORE_ENVIRONMENT=production restart: unless-stopped ports: - 5006:80 networks: - swiftparcel + lecturer-api-service: + image: adrianvsaint/swift-parcel-external-api-lecturer-service + container_name: lecturer-api-service + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + ports: + - 5005:80 + networks: + - swiftparcel + + api-gateway: + image: adrianvsaint/swift-parcel-apigateway-service + container_name: api-gateway + environment: + - ASPNETCORE_ENVIRONMENT=production + restart: unless-stopped + environment: + - NTRADA_CONFIG=ntrada.yml + ports: + - 5000:80 + networks: + - swiftparcel + networks: swiftparcel: diff --git a/SwiftParcel/xdeployment.yml b/SwiftParcel/xdeployment.yml new file mode 100644 index 0000000..b508f4c --- /dev/null +++ b/SwiftParcel/xdeployment.yml @@ -0,0 +1,96 @@ +version: "3.7" + +services: + # Microservices + identity-service: + image: adrianvsaint/swift-parcel-identity-service + container_name: identity-service + restart: unless-stopped + ports: + - 5292:80 + networks: + - swiftparcel + + web: + image: adrianvsaint/swift-parcel-web + container_name: swiftparcel-web + restart: unless-stopped + ports: + - 3001:80 + networks: + - swiftparcel + + deliveries-service: + image: adrianvsaint/swift-parcel-deliveries-service + container_name: deliveries-service + restart: unless-stopped + ports: + - 5003:80 + networks: + - swiftparcel + + parcels-service: + image: adrianvsaint/swift-parcel-parcels-service + container_name: parcels-service + restart: unless-stopped + ports: + - 5007:80 + networks: + - swiftparcel + + pricing-service: + image: adrianvsaint/swift-parcel-pricing-service + container_name: pricing-service + restart: unless-stopped + ports: + - 5008:80 + networks: + - swiftparcel + + customers-service: + image: adrianvsaint/swift-parcel-customers-service + container_name: customers-service + restart: unless-stopped + ports: + - 5002:80 + networks: + - swiftparcel + + orders-service: + image: adrianvsaint/swift-parcel-orders-service + container_name: orders-service + restart: unless-stopped + ports: + - 5006:80 + networks: + - swiftparcel + + lecturer-api-service: + image: adrianvsaint/swift-parcel-external-api-lecturer-service + container_name: lecturer-api-service + restart: unless-stopped + ports: + - 5004:80 + networks: + - swiftparcel + + api-gateway: + image: adrianvsaint/swift-parcel-apigateway-service + container_name: api-gateway + restart: unless-stopped + environment: + - NTRADA_CONFIG=ntrada.yml + ports: + - 5000:80 + networks: + - swiftparcel + +networks: + swiftparcel: + name: swiftparcel-network + +volumes: + mongo: + driver: local + redis: + driver: local diff --git a/SwifttParcel.Services.Identity/Dockerfile b/SwifttParcel.Services.Identity/Dockerfile index 4d85181..494325e 100644 --- a/SwifttParcel.Services.Identity/Dockerfile +++ b/SwifttParcel.Services.Identity/Dockerfile @@ -27,5 +27,5 @@ FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY --from=build /app/out ./ ENV ASPNETCORE_URLS http://*:80 -ENV ASPNETCORE_ENVIRONMENT docker +ENV ASPNETCORE_ENVIRONMENT production ENTRYPOINT ["dotnet", "SwiftParcel.Services.Identity.Api.dll"] diff --git a/SwifttParcel.Services.Identity/scripts/dockerize.sh b/SwifttParcel.Services.Identity/scripts/dockerize.sh new file mode 100755 index 0000000..109500b --- /dev/null +++ b/SwifttParcel.Services.Identity/scripts/dockerize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +export ASPNETCORE_ENVIRONMENT=Development + +cd ../ + +docker build -t swift-parcel-identity-service:latest . + +docker tag swift-parcel-identity-service:latest adrianvsaint/swift-parcel-identity-service:latest + +docker push adrianvsaint/swift-parcel-identity-service diff --git a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Program.cs b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Program.cs index 4db33e2..18a21a0 100644 --- a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Program.cs +++ b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Program.cs @@ -56,7 +56,7 @@ public static async Task Main(string[] args) .Post("sign-up", async (cmd, ctx) => { await ctx.RequestServices.GetService().SignUpAsync(cmd); - await ctx.Response.Created("identity/me"); + await ctx.Response.Created("identity/me", cmd.UserId); }) .Post("google-sign-up", async (cmd, ctx) => { diff --git a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Properties/launchSettings.json b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Properties/launchSettings.json index 6984d48..3742f4f 100644 --- a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Properties/launchSettings.json +++ b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/Properties/launchSettings.json @@ -19,7 +19,7 @@ }, "IIS Express": { "commandName": "IISExpress", - "launchBrowser": true, + "launchBrowser": false, "sslPort": 44300, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.json b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.json index d320b10..9eda7ed 100644 --- a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.json +++ b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.json @@ -104,13 +104,8 @@ }, "jwt": { - "certificate": { - "location": "certs/certificate.pfx", - "password": "", - "rawData": "" - }, - "issuerSigningKey": "eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImV4cCI6MTY5ODYwMTg3NSwiaWF0IjoxNjk4NjAxODc1fQ.nxJlaEy9xNO4noVh84qD9BSoLvjq1xu4LGhwBaEF9Ec", + "issuerSigningKey": "eiquief5phee9pazo0Faegaez9gohThailiur5woy2befiech1oarai4aiLi6ahVecah3ie9Aiz6Peij", "expiryMinutes": 60, "issuer": "swiftparcel", "validateAudience": false, diff --git a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.production.json b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.production.json new file mode 100644 index 0000000..d601bc6 --- /dev/null +++ b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/appsettings.production.json @@ -0,0 +1,165 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "consul": { + "enabled": true, + "url": "http://consul:8500", + "service": "identity-service", + "address": "identity-service", + "port": "80", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://fabio:9999", + "service": "identity-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": {}, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "AllowedHosts": "*", + "vault": { + "enabled": false, + "url": "http://127.0.0.1:8200", + "authType": "userpass", + "token": "secret", + "username": "user", + "password": "piotr-amadeusz-andrii-2023", + "dbusername": "andrii-courier-db-user", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "secret", + "path": "identity-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "identity-service", + "commonName": "identity-service.swiftparcel.com" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "identity-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority" + } + } + } + }, + "GoogleAuth": { + "ClientId": "YourGoogleClientId", + "ClientSecret": "YourGoogleClientSecret" + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://elk:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://seq:5341", + "apiKey": "secret" + }, + "tags": {} + }, + + "jwt": { + "certificate": { + "location": "", + "password": "", + "rawData": "" + }, + + "issuerSigningKey": "eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImV4cCI6MTY5ODYwMTg3NSwiaWF0IjoxNjk4NjAxODc1fQ.nxJlaEy9xNO4noVh84qD9BSoLvjq1xu4LGhwBaEF9Ec", + "expiryMinutes": 60, + "issuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": false, + "validateLifetime": true, + "allowAnonymousEndpoints": ["/sign-in", "/sign-up"] + }, + + "GoogleAuthSettings": { + "ClientId": "", + "ClientSecret": "" + }, + + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + "metrics": { + "enabled": true, + "influxEnabled": false, + "prometheusEnabled": true, + "influxUrl": "http://influx:8086", + "database": "swift-parcel", + "env": "docker", + "interval": 5 + }, + "jaeger": { + "enabled": true, + "serviceName": "identity", + "udpHost": "jaeger", + "udpPort": 6831, + "maxPacketSize": 0, + "sampler": "const", + "excludePaths": ["/", "/ping", "/metrics"] + }, + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "identity-service", + "seed": false + }, + "rabbitMq": { + "port": 5672, + "hostnames": [ + "rabbitmq" + ] + } +} diff --git a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/development.appsettings.json b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/development.appsettings.json new file mode 100644 index 0000000..d374231 --- /dev/null +++ b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Api/SwiftParcel.Services.Identity.Api/development.appsettings.json @@ -0,0 +1,185 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "consul": { + "enabled": true, + "url": "http://localhost:8500", + "service": "identity-service", + "address": "docker.for.win.localhost", + "port": "6001", + "pingEnabled": true, + "pingEndpoint": "ping", + "pingInterval": 3, + "removeAfterInterval": 3 + }, + "fabio": { + "enabled": true, + "url": "http://localhost:9999", + "service": "identity-service" + }, + "httpClient": { + "type": "fabio", + "retries": 3, + "services": {}, + "requestMasking": { + "enabled": true, + "maskTemplate": "*****" + } + }, + "AllowedHosts": "*", + "vault": { + "enabled": false, + "url": "http://127.0.0.1:8200", + "authType": "userpass", + "token": "secret", + "username": "user", + "password": "piotr-amadeusz-andrii-2023", + "dbusername": "andrii-courier-db-user", + "kv": { + "enabled": true, + "engineVersion": 2, + "mountPoint": "secret", + "path": "identity-service/settings" + }, + "pki": { + "enabled": false, + "roleName": "identity-service", + "commonName": "identity-service.swiftparcel.com" + }, + "lease": { + "mongo": { + "type": "database", + "roleName": "identity-service", + "enabled": true, + "autoRenewal": true, + "templates": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority" + } + } + } + }, + "GoogleAuth": { + "ClientId": "YourGoogleClientId", + "ClientSecret": "YourGoogleClientSecret" + }, + "logger": { + "level": "information", + "excludePaths": ["/", "/ping", "/metrics"], + "excludeProperties": [ + "api_key", + "access_key", + "ApiKey", + "ApiSecret", + "ClientId", + "ClientSecret", + "ConnectionString", + "Password", + "Email", + "Login", + "Secret", + "Token" + ], + "console": { + "enabled": true + }, + "elk": { + "enabled": false, + "url": "http://localhost:9200" + }, + "file": { + "enabled": true, + "path": "logs/logs.txt", + "interval": "day" + }, + "seq": { + "enabled": true, + "url": "http://localhost:5341", + "apiKey": "secret" + }, + "tags": {} + }, + + "jwt": { + "certificate": { + "location": "certs/certificate.pfx", + "password": "", + "rawData": "" + }, + + "issuerSigningKey": "eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImV4cCI6MTY5ODYwMTg3NSwiaWF0IjoxNjk4NjAxODc1fQ.nxJlaEy9xNO4noVh84qD9BSoLvjq1xu4LGhwBaEF9Ec", + "expiryMinutes": 60, + "issuer": "swiftparcel", + "validateAudience": false, + "validateIssuer": false, + "validateLifetime": true, + "allowAnonymousEndpoints": ["/sign-in", "/sign-up"] + }, + + "GoogleAuthSettings": { + "ClientId": "", + "ClientSecret": "" + }, + + "swagger": { + "enabled": true, + "reDocEnabled": false, + "name": "v1", + "title": "API", + "version": "v1", + "routePrefix": "docs", + "includeSecurity": true + }, + + "mongo": { + "connectionString": "mongodb+srv://andrii-courier-db-user:piotr-amadeusz-andrii-2023@cluster0.br51nsv.mongodb.net/?retryWrites=true&w=majority", + "database": "identity-service", + "seed": false + }, + "rabbitMq": { + "connectionName": "identity-service", + "retries": 3, + "retryInterval": 2, + "conventionsCasing": "snakeCase", + "logger": { + "enabled": true + }, + "username": "guest", + "password": "guest", + "virtualHost": "/", + "port": 5672, + "hostnames": [ + "localhost" + ], + "requestedConnectionTimeout": "00:00:30", + "requestedHeartbeat": "00:01:00", + "socketReadTimeout": "00:00:30", + "socketWriteTimeout": "00:00:30", + "continuationTimeout": "00:00:20", + "handshakeContinuationTimeout": "00:00:10", + "networkRecoveryInterval": "00:00:05", + "exchange": { + "declare": true, + "durable": true, + "autoDelete": false, + "type": "topic", + "name": "identity" + }, + "queue": { + "declare": true, + "durable": true, + "exclusive": false, + "autoDelete": false, + "template": "identity-service/{{exchange}}.{{message}}" + }, + "context": { + "enabled": true, + "header": "message_context" + }, + "spanContextHeader": "span_context" + } + } + \ No newline at end of file diff --git a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Infrastructure/SwiftParcel.Services.Identity.Infrastructure/Extensions.cs b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Infrastructure/SwiftParcel.Services.Identity.Infrastructure/Extensions.cs index 0614411..1511020 100644 --- a/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Infrastructure/SwiftParcel.Services.Identity.Infrastructure/Extensions.cs +++ b/SwifttParcel.Services.Identity/src/SwiftParcel.Services.Identity.Infrastructure/SwiftParcel.Services.Identity.Infrastructure/Extensions.cs @@ -82,12 +82,12 @@ public static IConveyBuilder AddInfrastructure(this IConveyBuilder builder) builder.Services.AddAuthentication() - .AddGoogle(options => - { - options.ClientId = ""; - options.ClientSecret = ""; + .AddGoogle(options => + { + options.ClientId = "396949671840-ojodge4lc03jf2jn15kljfi4krpt2c6k.apps.googleusercontent.com"; + options.ClientSecret = "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"; - }); + }); return builder