瀏覽代碼

WIP refactor container and queuing system (#206)

* refactor microservices to machine-learning

* Update tGithub issue template with correct task syntax

* Added microservices container

* Communicate between service based on queue system

* added dependency

* Fixed problem with having to import BullQueue into the individual service

* Added todo

* refactor server into monorepo with microservices

* refactor database and entity to library

* added simple migration

* Move migrations and database config to library

* Migration works in library

* Cosmetic change in logging message

* added user dto

* Fixed issue with testing not able to find the shared library

* Clean up library mapping path

* Added webp generator to microservices

* Update Github Action build latest

* Fixed issue NPM cannot install due to conflict witl Bull Queue

* format project with prettier

* Modified docker-compose file

* Add GH Action for Staging build:

* Fixed GH action job name

* Modified GH Action to only build & push latest when pushing to main

* Added Test 2e2 Github Action

* Added Test 2e2 Github Action

* Implemented microservice to extract exif

* Added cronjob to scan and generate webp thumbnail  at midnight

* Refactor to ireduce hit time to database when running microservices

* Added error handling to asset services that handle read file from disk

* Added video transcoding queue to process one video at a time

* Fixed loading spinner on web while loading covering the info panel

* Add mechanism to show new release announcement to web and mobile app (#209)

* Added changelog page

* Fixed issues based on PR comments

* Fixed issue with video transcoding run on the server

* Change entry point content for backward combatibility when starting up server

* Added announcement box

* Added error handling to failed silently when the app version checking is not able to make the request to GITHUB

* Added new version announcement overlay

* Update message

* Added messages

* Added logic to check and show announcement

* Add method to handle saving new version

* Added button to dimiss the acknowledge message

* Up version for deployment to the app store
Alex 3 年之前
父節點
當前提交
a8220172f8
共有 100 個文件被更改,包括 693 次插入1377 次删除
  1. 4 4
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 12 15
      .github/workflows/build_push_docker_latest.yml
  3. 95 0
      .github/workflows/build_push_docker_staging.yml
  4. 24 5
      docker/docker-compose.dev.yml
  5. 22 17
      docker/docker-compose.staging.yml
  6. 20 6
      docker/docker-compose.yml
  7. 0 0
      machine-learning/.dockerignore
  8. 0 0
      machine-learning/.eslintrc.js
  9. 3 1
      machine-learning/.gitignore
  10. 0 0
      machine-learning/.prettierrc
  11. 0 0
      machine-learning/Dockerfile
  12. 0 0
      machine-learning/README.md
  13. 0 0
      machine-learning/entrypoint.sh
  14. 0 0
      machine-learning/nest-cli.json
  15. 0 0
      machine-learning/package-lock.json
  16. 0 0
      machine-learning/package.json
  17. 0 0
      machine-learning/src/app.module.ts
  18. 0 0
      machine-learning/src/config/database.config.ts
  19. 1 1
      machine-learning/src/image-classifier/image-classifier.controller.ts
  20. 0 0
      machine-learning/src/image-classifier/image-classifier.module.ts
  21. 0 0
      machine-learning/src/image-classifier/image-classifier.service.ts
  22. 2 2
      machine-learning/src/main.ts
  23. 1 1
      machine-learning/src/object-detection/object-detection.controller.ts
  24. 0 0
      machine-learning/src/object-detection/object-detection.module.ts
  25. 0 0
      machine-learning/src/object-detection/object-detection.service.ts
  26. 0 0
      machine-learning/tsconfig.build.json
  27. 0 0
      machine-learning/tsconfig.json
  28. 0 1
      machine_learning/.dockerignore
  29. 0 3
      machine_learning/.gitignore
  30. 0 25
      machine_learning/Dockerfile
  31. 0 0
      machine_learning/app/__init__.py
  32. 二進制
      machine_learning/app/cars.jpg
  33. 0 0
      machine_learning/app/image_classifier/__init__.py
  34. 0 37
      machine_learning/app/image_classifier/image_classifier.py
  35. 0 1002
      machine_learning/app/imagenet_class_index.json
  36. 0 46
      machine_learning/app/main.py
  37. 0 0
      machine_learning/app/object_detection/__init__.py
  38. 0 4
      machine_learning/app/object_detection/object_detection.py
  39. 二進制
      machine_learning/app/test.png
  40. 0 8
      machine_learning/requirements.txt
  41. 7 0
      mobile/android/app/src/main/AndroidManifest.xml
  42. 1 0
      mobile/android/fastlane/metadata/android/en-US/changelogs/17.txt
  43. 7 1
      mobile/ios/Runner/Info.plist
  44. 1 1
      mobile/ios/fastlane/Fastfile
  45. 4 0
      mobile/lib/constants/hive_box.dart
  46. 18 3
      mobile/lib/main.dart
  47. 57 0
      mobile/lib/shared/providers/release_info.provider.dart
  48. 0 5
      mobile/lib/shared/providers/server_info.provider.dart
  49. 10 8
      mobile/lib/shared/services/server_info.service.dart
  50. 133 0
      mobile/lib/shared/views/version_announcement_overlay.dart
  51. 56 0
      mobile/pubspec.lock
  52. 2 1
      mobile/pubspec.yaml
  53. 1 1
      server/Dockerfile
  54. 23 7
      server/apps/immich/src/api-v1/asset/asset.controller.ts
  55. 6 18
      server/apps/immich/src/api-v1/asset/asset.module.ts
  56. 134 115
      server/apps/immich/src/api-v1/asset/asset.service.ts
  57. 1 1
      server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts
  58. 0 0
      server/apps/immich/src/api-v1/asset/dto/create-exif.dto.ts
  59. 0 0
      server/apps/immich/src/api-v1/asset/dto/delete-asset.dto.ts
  60. 0 0
      server/apps/immich/src/api-v1/asset/dto/get-all-asset-query.dto.ts
  61. 1 1
      server/apps/immich/src/api-v1/asset/dto/get-all-asset-response.dto.ts
  62. 0 0
      server/apps/immich/src/api-v1/asset/dto/get-asset.dto.ts
  63. 0 0
      server/apps/immich/src/api-v1/asset/dto/get-new-asset-query.dto.ts
  64. 0 0
      server/apps/immich/src/api-v1/asset/dto/search-asset.dto.ts
  65. 0 0
      server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts
  66. 0 0
      server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts
  67. 0 0
      server/apps/immich/src/api-v1/asset/dto/update-exif.dto.ts
  68. 1 1
      server/apps/immich/src/api-v1/auth/auth.controller.ts
  69. 1 1
      server/apps/immich/src/api-v1/auth/auth.module.ts
  70. 1 1
      server/apps/immich/src/api-v1/auth/auth.service.ts
  71. 0 0
      server/apps/immich/src/api-v1/auth/dto/jwt-payload.dto.ts
  72. 0 0
      server/apps/immich/src/api-v1/auth/dto/login-credential.dto.ts
  73. 0 0
      server/apps/immich/src/api-v1/auth/dto/sign-up.dto.ts
  74. 1 1
      server/apps/immich/src/api-v1/communication/communication.gateway.ts
  75. 1 1
      server/apps/immich/src/api-v1/communication/communication.module.ts
  76. 0 0
      server/apps/immich/src/api-v1/communication/communication.service.ts
  77. 0 0
      server/apps/immich/src/api-v1/device-info/device-info.controller.ts
  78. 1 1
      server/apps/immich/src/api-v1/device-info/device-info.module.ts
  79. 1 1
      server/apps/immich/src/api-v1/device-info/device-info.service.ts
  80. 1 1
      server/apps/immich/src/api-v1/device-info/dto/create-device-info.dto.ts
  81. 1 1
      server/apps/immich/src/api-v1/device-info/dto/update-device-info.dto.ts
  82. 0 0
      server/apps/immich/src/api-v1/server-info/dto/server-info.dto.ts
  83. 0 0
      server/apps/immich/src/api-v1/server-info/server-info.controller.ts
  84. 1 1
      server/apps/immich/src/api-v1/server-info/server-info.module.ts
  85. 0 0
      server/apps/immich/src/api-v1/server-info/server-info.service.ts
  86. 1 1
      server/apps/immich/src/api-v1/sharing/dto/add-assets.dto.ts
  87. 0 0
      server/apps/immich/src/api-v1/sharing/dto/add-users.dto.ts
  88. 1 1
      server/apps/immich/src/api-v1/sharing/dto/create-shared-album.dto.ts
  89. 0 0
      server/apps/immich/src/api-v1/sharing/dto/remove-assets.dto.ts
  90. 0 0
      server/apps/immich/src/api-v1/sharing/dto/update-shared-album.dto.ts
  91. 0 0
      server/apps/immich/src/api-v1/sharing/sharing.controller.ts
  92. 5 5
      server/apps/immich/src/api-v1/sharing/sharing.module.ts
  93. 5 5
      server/apps/immich/src/api-v1/sharing/sharing.service.ts
  94. 0 0
      server/apps/immich/src/api-v1/user/dto/create-user.dto.ts
  95. 0 0
      server/apps/immich/src/api-v1/user/dto/update-user.dto.ts
  96. 1 1
      server/apps/immich/src/api-v1/user/response-dto/user.ts
  97. 19 7
      server/apps/immich/src/api-v1/user/user.controller.ts
  98. 2 2
      server/apps/immich/src/api-v1/user/user.module.ts
  99. 1 1
      server/apps/immich/src/api-v1/user/user.service.ts
  100. 2 4
      server/apps/immich/src/app.controller.ts

+ 4 - 4
.github/ISSUE_TEMPLATE/bug_report.md

@@ -16,10 +16,10 @@ Note: Please search to see if an issue already exists for the bug you encountere
 A clear and concise description of what the bug is.
 
 **Task List**
-[ ] I have read thoroughly the README setup and installation instructions.
-[ ] If my setup is different, I have included my docker-compose file.
-[ ] I have included my redacted `.env` file.
-[ ] I have included information on my machine, and environment.
+- [ ] I have read thoroughly the README setup and installation instructions.
+- [ ] If my setup is different, I have included my docker-compose file.
+- [ ] I have included my redacted `.env` file.
+- [ ] I have included information on my machine, and environment.
 
 **To Reproduce**
 Steps to reproduce the behavior:

+ 12 - 15
.github/workflows/build_push_docker_latest.yml

@@ -4,17 +4,16 @@ on:
   workflow_dispatch:
   push:
     branches: [main]
-  pull_request:
-    branches: [main]
 
 jobs:
-  build_and_push_server_latest:
+  # This image include both the server and microservices - the two containers can be slitted into separated
+  # service with its coressponding entry file.
+  build_and_push_server_monorepo_latest:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
         uses: actions/checkout@v3
         with:
-          # ref: "main" # branch
           fetch-depth: 0
 
       - name: Set up QEMU
@@ -27,23 +26,22 @@ jobs:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
-      - name: Build and push Immich
+      - name: Build and push Immich Mono Repo
         uses: docker/build-push-action@v3.0.0
         with:
           context: ./server
           file: ./server/Dockerfile
           platforms: linux/arm/v7,linux/amd64,linux/arm64
-          push: ${{ github.event_name != 'pull_request' }}
+          push: true
           tags: |
             altran1502/immich-server:latest
 
-  build_and_push_microservice_latest:
+  build_and_push_machine_learning_latest:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
         uses: actions/checkout@v3
         with:
-          # ref: "main" # branch
           fetch-depth: 0
 
       - name: Set up QEMU
@@ -56,15 +54,15 @@ jobs:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
-      - name: Build and Push Microservices
+      - name: Build and Push Machine Learning
         uses: docker/build-push-action@v3.0.0
         with:
-          context: ./microservices
-          file: ./microservices/Dockerfile
+          context: ./machine-learning
+          file: ./machine-learning/Dockerfile
           platforms: linux/arm/v7,linux/amd64
-          push: ${{ github.event_name != 'pull_request' }}
+          push: true
           tags: |
-            altran1502/immich-microservices:latest
+            altran1502/immich-machine-learning:latest
 
   build_and_push_web_latest:
     runs-on: ubuntu-latest
@@ -72,7 +70,6 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v3
         with:
-          # ref: "main" # branch
           fetch-depth: 0
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v2.0.0
@@ -91,6 +88,6 @@ jobs:
           file: ./web/Dockerfile
           platforms: linux/arm/v7,linux/amd64,linux/arm64
           target: prod
-          push: ${{ github.event_name != 'pull_request' }}
+          push: true
           tags: |
             altran1502/immich-web:latest

+ 95 - 0
.github/workflows/build_push_docker_staging.yml

@@ -0,0 +1,95 @@
+name: Build and Push Docker Image - Staging
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+
+jobs:
+  # This image include both the server and microservices - the two containers can be slitted into separated
+  # service with its coressponding entry file.
+  build_and_push_server_monorepo_staging:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v2.0.0
+      - name: Set up Docker Buildx
+        id: buildx
+        uses: docker/setup-buildx-action@v2.0.0
+      - name: Login to Docker Hub
+        uses: docker/login-action@v2
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+      - name: Build and push Immich Mono Repo
+        uses: docker/build-push-action@v3.0.0
+        with:
+          context: ./server
+          file: ./server/Dockerfile
+          platforms: linux/arm/v7,linux/amd64,linux/arm64
+          push: ${{ github.event_name == 'pull_request' }}
+          tags: |
+            altran1502/immich-server:staging
+
+  build_and_push_machine_learning_staging:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v2.0.0
+      - name: Set up Docker Buildx
+        id: buildx
+        uses: docker/setup-buildx-action@v2.0.0
+      - name: Login to Docker Hub
+        uses: docker/login-action@v2
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+      - name: Build and Push Machine Learning
+        uses: docker/build-push-action@v3.0.0
+        with:
+          context: ./machine-learning
+          file: ./machine-learning/Dockerfile
+          platforms: linux/arm/v7,linux/amd64
+          push: ${{ github.event_name == 'pull_request' }}
+          tags: |
+            altran1502/immich-machine-learning:staging
+
+  build_and_push_web_staging:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v2.0.0
+      - name: Set up Docker Buildx
+        id: buildx
+        uses: docker/setup-buildx-action@v2.0.0
+      - name: Login to Docker Hub
+        uses: docker/login-action@v2
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+      - name: Build and Push Web
+        uses: docker/build-push-action@v3.0.0
+        with:
+          context: ./web
+          file: ./web/Dockerfile
+          platforms: linux/arm/v7,linux/amd64,linux/arm64
+          target: prod
+          push: ${{ github.event_name == 'pull_request' }}
+          tags: |
+            altran1502/immich-web:staging

+ 24 - 5
docker/docker-compose.dev.yml

@@ -6,7 +6,7 @@ services:
     build:
       context: ../server
       dockerfile: Dockerfile
-    command: npm run start:dev
+    command: npm run start:dev immich
     expose:
       - "3000"
     volumes:
@@ -23,16 +23,35 @@ services:
     networks:
       - immich-network
 
-  immich-microservices:
-    image: immich-microservices-dev:1.9.0
+  immich-machine-learning:
+    image: immich-machine-learning-dev:1.9.0
     build:
-      context: ../microservices
+      context: ../machine-learning
       dockerfile: Dockerfile
     command: npm run start:dev
     expose:
       - "3001"
     volumes:
-      - ../microservices:/usr/src/app
+      - ../machine-learning:/usr/src/app
+      - ${UPLOAD_LOCATION}:/usr/src/app/upload
+      - /usr/src/app/node_modules
+    env_file:
+      - .env
+    environment:
+      - NODE_ENV=development
+    depends_on:
+      - database
+    networks:
+      - immich-network
+
+  immich-microservices:
+    image: immich-microservices:1.9.0
+    build:
+      context: ../server
+      dockerfile: Dockerfile
+    command: npm run start:dev microservices
+    volumes:
+      - ../server:/usr/src/app
       - ${UPLOAD_LOCATION}:/usr/src/app/upload
       - /usr/src/app/node_modules
     env_file:

+ 22 - 17
docker/docker-compose.staging.yml

@@ -2,11 +2,8 @@ version: "3.8"
 
 services:
   immich-server:
-    image: immich-server-staging:latest
-    build:
-      context: ../server
-      dockerfile: Dockerfile
-    entrypoint: ["/bin/sh", "./entrypoint.sh"]
+    image: altran1502/immich-server:staging
+    entrypoint: ["/bin/sh", "./start-server.sh"]
     expose:
       - "3000"
     volumes:
@@ -23,10 +20,23 @@ services:
     restart: always
 
   immich-microservices:
-    image: immich-microservices-staging:latest
-    build:
-      context: ../microservices
-      dockerfile: Dockerfile
+    image: altran1502/immich-server:staging
+    entrypoint: ["/bin/sh", "./start-microservices.sh"]
+    volumes:
+      - ${UPLOAD_LOCATION}:/usr/src/app/upload
+    env_file:
+      - .env
+    environment:
+      - NODE_ENV=production
+    depends_on:
+      - redis
+      - database
+    networks:
+      - immich-network
+    restart: always
+
+  immich-machine-learning:
+    image: altran1502/immich-machine-learning:staging
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
     expose:
       - "3001"
@@ -43,12 +53,8 @@ services:
     restart: always
 
   immich-web:
-    image: immich-web-staging:latest
+    image: altran1502/immich-web:staging
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
-    build:
-      context: ../web
-      dockerfile: Dockerfile
-      target: prod
     env_file:
       - .env
     ports:
@@ -57,14 +63,12 @@ services:
       - immich-network
     restart: always
 
-
   redis:
     container_name: immich_redis
     image: redis:6.2
     networks:
       - immich-network
     restart: always
-    
 
   database:
     container_name: immich_postgres
@@ -82,6 +86,7 @@ services:
       - 5432:5432
     networks:
       - immich-network
+    restart: always
 
   nginx:
     container_name: proxy_nginx
@@ -102,4 +107,4 @@ services:
 networks:
   immich-network:
 volumes:
-  pgdata:
+  pgdata:

+ 20 - 6
docker/docker-compose.yml

@@ -3,7 +3,7 @@ version: "3.8"
 services:
   immich-server:
     image: altran1502/immich-server:latest
-    entrypoint: ["/bin/sh", "./entrypoint.sh"]
+    entrypoint: ["/bin/sh", "./start-server.sh"]
     expose:
       - "3000"
     volumes:
@@ -20,7 +20,23 @@ services:
     restart: always
 
   immich-microservices:
-    image: altran1502/immich-microservices:latest
+    image: altran1502/immich-server:latest
+    entrypoint: ["/bin/sh", "./start-microservices.sh"]
+    volumes:
+      - ${UPLOAD_LOCATION}:/usr/src/app/upload
+    env_file:
+      - .env
+    environment:
+      - NODE_ENV=production
+    depends_on:
+      - redis
+      - database
+    networks:
+      - immich-network
+    restart: always
+
+  immich-machine-learning:
+    image: altran1502/immich-machine-learning:latest
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
     expose:
       - "3001"
@@ -47,14 +63,12 @@ services:
       - immich-network
     restart: always
 
-
   redis:
     container_name: immich_redis
     image: redis:6.2
     networks:
       - immich-network
     restart: always
-    
 
   database:
     container_name: immich_postgres
@@ -73,7 +87,7 @@ services:
     networks:
       - immich-network
     restart: always
-    
+
   nginx:
     container_name: proxy_nginx
     image: nginx:latest
@@ -93,4 +107,4 @@ services:
 networks:
   immich-network:
 volumes:
-  pgdata:
+  pgdata:

+ 0 - 0
microservices/.dockerignore → machine-learning/.dockerignore


+ 0 - 0
microservices/.eslintrc.js → machine-learning/.eslintrc.js


+ 3 - 1
microservices/.gitignore → machine-learning/.gitignore

@@ -32,4 +32,6 @@ lerna-debug.log*
 !.vscode/settings.json
 !.vscode/tasks.json
 !.vscode/launch.json
-!.vscode/extensions.json
+!.vscode/extensions.json
+
+upload/

+ 0 - 0
microservices/.prettierrc → machine-learning/.prettierrc


+ 0 - 0
microservices/Dockerfile → machine-learning/Dockerfile


+ 0 - 0
microservices/README.md → machine-learning/README.md


+ 0 - 0
microservices/entrypoint.sh → machine-learning/entrypoint.sh


+ 0 - 0
microservices/nest-cli.json → machine-learning/nest-cli.json


+ 0 - 0
microservices/package-lock.json → machine-learning/package-lock.json


+ 0 - 0
microservices/package.json → machine-learning/package.json


+ 0 - 0
microservices/src/app.module.ts → machine-learning/src/app.module.ts


+ 0 - 0
microservices/src/config/database.config.ts → machine-learning/src/config/database.config.ts


+ 1 - 1
microservices/src/image-classifier/image-classifier.controller.ts → machine-learning/src/image-classifier/image-classifier.controller.ts

@@ -7,7 +7,7 @@ export class ImageClassifierController {
     private readonly imageClassifierService: ImageClassifierService,
   ) { }
 
-  @Post('/tagImage')
+  @Post('/tag-image')
   async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
     return await this.imageClassifierService.tagImage(thumbnailPath);
   }

+ 0 - 0
microservices/src/image-classifier/image-classifier.module.ts → machine-learning/src/image-classifier/image-classifier.module.ts


+ 0 - 0
microservices/src/image-classifier/image-classifier.service.ts → machine-learning/src/image-classifier/image-classifier.service.ts


+ 2 - 2
microservices/src/main.ts → machine-learning/src/main.ts

@@ -8,14 +8,14 @@ async function bootstrap() {
   await app.listen(3001, () => {
     if (process.env.NODE_ENV == 'development') {
       Logger.log(
-        'Running Immich Microservices in DEVELOPMENT environment',
+        'Running Immich Machine Learning in DEVELOPMENT environment',
         'IMMICH MICROSERVICES',
       );
     }
 
     if (process.env.NODE_ENV == 'production') {
       Logger.log(
-        'Running Immich Microservices in PRODUCTION environment',
+        'Running Immich Machine Learning in PRODUCTION environment',
         'IMMICH MICROSERVICES',
       );
     }

+ 1 - 1
microservices/src/object-detection/object-detection.controller.ts → machine-learning/src/object-detection/object-detection.controller.ts

@@ -8,7 +8,7 @@ export class ObjectDetectionController {
     private readonly objectDetectionService: ObjectDetectionService,
   ) { }
 
-  @Post('/detectObject')
+  @Post('/detect-object')
   async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
     return await this.objectDetectionService.detectObject(thumbnailPath);
   }

+ 0 - 0
microservices/src/object-detection/object-detection.module.ts → machine-learning/src/object-detection/object-detection.module.ts


+ 0 - 0
microservices/src/object-detection/object-detection.service.ts → machine-learning/src/object-detection/object-detection.service.ts


+ 0 - 0
microservices/tsconfig.build.json → machine-learning/tsconfig.build.json


+ 0 - 0
microservices/tsconfig.json → machine-learning/tsconfig.json


+ 0 - 1
machine_learning/.dockerignore

@@ -1 +0,0 @@
-devenv/

+ 0 - 3
machine_learning/.gitignore

@@ -1,3 +0,0 @@
-__pycache__/
-devenv/
-app/upload

+ 0 - 25
machine_learning/Dockerfile

@@ -1,25 +0,0 @@
-## GPU Build
-# FROM tensorflow/tensorflow:latest-gpu as gpu
-
-# WORKDIR /code
-
-# COPY ./requirements.txt /code/requirements.txt
-
-# RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
-
-# COPY ./app /code/app
-
-
-## CPU BUILD
-FROM python:3.8 as cpu
-
-RUN apt-get update
-RUN apt-get install ffmpeg libsm6 libxext6  -y
-
-WORKDIR /code
-
-COPY ./requirements.txt /code/requirements.txt
-
-RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
-
-COPY ./app /code/app

+ 0 - 0
machine_learning/app/__init__.py


二進制
machine_learning/app/cars.jpg


+ 0 - 0
machine_learning/app/image_classifier/__init__.py


+ 0 - 37
machine_learning/app/image_classifier/image_classifier.py

@@ -1,37 +0,0 @@
-from tensorflow.keras.applications import InceptionV3
-from tensorflow.keras.applications.inception_v3 import preprocess_input, decode_predictions
-from tensorflow.keras.preprocessing import image
-import numpy as np
-from PIL import Image
-import cv2
-IMG_SIZE = 299
-PREDICTION_MODEL = InceptionV3(weights='imagenet')
-
-
-def classify_image(image_path: str):
-    img_path = f'./app/{image_path}'
-    # img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))
-
-    target_image = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
-    resized_target_image = cv2.resize(target_image, (IMG_SIZE, IMG_SIZE))
-
-    x = image.img_to_array(resized_target_image)
-    x = np.expand_dims(x, axis=0)
-    x = preprocess_input(x)
-
-    preds = PREDICTION_MODEL.predict(x)
-    result = decode_predictions(preds, top=3)[0]
-    payload = []
-    for _, value, _ in result:
-        payload.append(value)
-
-    return payload
-
-
-def warm_up():
-    img_path = f'./app/test.png'
-    img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))
-    x = image.img_to_array(img)
-    x = np.expand_dims(x, axis=0)
-    x = preprocess_input(x)
-    PREDICTION_MODEL.predict(x)

+ 0 - 1002
machine_learning/app/imagenet_class_index.json

@@ -1,1002 +0,0 @@
-{
-  "0": ["n01440764", "tench"],
-  "1": ["n01443537", "goldfish"],
-  "2": ["n01484850", "great_white_shark"],
-  "3": ["n01491361", "tiger_shark"],
-  "4": ["n01494475", "hammerhead"],
-  "5": ["n01496331", "electric_ray"],
-  "6": ["n01498041", "stingray"],
-  "7": ["n01514668", "cock"],
-  "8": ["n01514859", "hen"],
-  "9": ["n01518878", "ostrich"],
-  "10": ["n01530575", "brambling"],
-  "11": ["n01531178", "goldfinch"],
-  "12": ["n01532829", "house_finch"],
-  "13": ["n01534433", "junco"],
-  "14": ["n01537544", "indigo_bunting"],
-  "15": ["n01558993", "robin"],
-  "16": ["n01560419", "bulbul"],
-  "17": ["n01580077", "jay"],
-  "18": ["n01582220", "magpie"],
-  "19": ["n01592084", "chickadee"],
-  "20": ["n01601694", "water_ouzel"],
-  "21": ["n01608432", "kite"],
-  "22": ["n01614925", "bald_eagle"],
-  "23": ["n01616318", "vulture"],
-  "24": ["n01622779", "great_grey_owl"],
-  "25": ["n01629819", "European_fire_salamander"],
-  "26": ["n01630670", "common_newt"],
-  "27": ["n01631663", "eft"],
-  "28": ["n01632458", "spotted_salamander"],
-  "29": ["n01632777", "axolotl"],
-  "30": ["n01641577", "bullfrog"],
-  "31": ["n01644373", "tree_frog"],
-  "32": ["n01644900", "tailed_frog"],
-  "33": ["n01664065", "loggerhead"],
-  "34": ["n01665541", "leatherback_turtle"],
-  "35": ["n01667114", "mud_turtle"],
-  "36": ["n01667778", "terrapin"],
-  "37": ["n01669191", "box_turtle"],
-  "38": ["n01675722", "banded_gecko"],
-  "39": ["n01677366", "common_iguana"],
-  "40": ["n01682714", "American_chameleon"],
-  "41": ["n01685808", "whiptail"],
-  "42": ["n01687978", "agama"],
-  "43": ["n01688243", "frilled_lizard"],
-  "44": ["n01689811", "alligator_lizard"],
-  "45": ["n01692333", "Gila_monster"],
-  "46": ["n01693334", "green_lizard"],
-  "47": ["n01694178", "African_chameleon"],
-  "48": ["n01695060", "Komodo_dragon"],
-  "49": ["n01697457", "African_crocodile"],
-  "50": ["n01698640", "American_alligator"],
-  "51": ["n01704323", "triceratops"],
-  "52": ["n01728572", "thunder_snake"],
-  "53": ["n01728920", "ringneck_snake"],
-  "54": ["n01729322", "hognose_snake"],
-  "55": ["n01729977", "green_snake"],
-  "56": ["n01734418", "king_snake"],
-  "57": ["n01735189", "garter_snake"],
-  "58": ["n01737021", "water_snake"],
-  "59": ["n01739381", "vine_snake"],
-  "60": ["n01740131", "night_snake"],
-  "61": ["n01742172", "boa_constrictor"],
-  "62": ["n01744401", "rock_python"],
-  "63": ["n01748264", "Indian_cobra"],
-  "64": ["n01749939", "green_mamba"],
-  "65": ["n01751748", "sea_snake"],
-  "66": ["n01753488", "horned_viper"],
-  "67": ["n01755581", "diamondback"],
-  "68": ["n01756291", "sidewinder"],
-  "69": ["n01768244", "trilobite"],
-  "70": ["n01770081", "harvestman"],
-  "71": ["n01770393", "scorpion"],
-  "72": ["n01773157", "black_and_gold_garden_spider"],
-  "73": ["n01773549", "barn_spider"],
-  "74": ["n01773797", "garden_spider"],
-  "75": ["n01774384", "black_widow"],
-  "76": ["n01774750", "tarantula"],
-  "77": ["n01775062", "wolf_spider"],
-  "78": ["n01776313", "tick"],
-  "79": ["n01784675", "centipede"],
-  "80": ["n01795545", "black_grouse"],
-  "81": ["n01796340", "ptarmigan"],
-  "82": ["n01797886", "ruffed_grouse"],
-  "83": ["n01798484", "prairie_chicken"],
-  "84": ["n01806143", "peacock"],
-  "85": ["n01806567", "quail"],
-  "86": ["n01807496", "partridge"],
-  "87": ["n01817953", "African_grey"],
-  "88": ["n01818515", "macaw"],
-  "89": ["n01819313", "sulphur-crested_cockatoo"],
-  "90": ["n01820546", "lorikeet"],
-  "91": ["n01824575", "coucal"],
-  "92": ["n01828970", "bee_eater"],
-  "93": ["n01829413", "hornbill"],
-  "94": ["n01833805", "hummingbird"],
-  "95": ["n01843065", "jacamar"],
-  "96": ["n01843383", "toucan"],
-  "97": ["n01847000", "drake"],
-  "98": ["n01855032", "red-breasted_merganser"],
-  "99": ["n01855672", "goose"],
-  "100": ["n01860187", "black_swan"],
-  "101": ["n01871265", "tusker"],
-  "102": ["n01872401", "echidna"],
-  "103": ["n01873310", "platypus"],
-  "104": ["n01877812", "wallaby"],
-  "105": ["n01882714", "koala"],
-  "106": ["n01883070", "wombat"],
-  "107": ["n01910747", "jellyfish"],
-  "108": ["n01914609", "sea_anemone"],
-  "109": ["n01917289", "brain_coral"],
-  "110": ["n01924916", "flatworm"],
-  "111": ["n01930112", "nematode"],
-  "112": ["n01943899", "conch"],
-  "113": ["n01944390", "snail"],
-  "114": ["n01945685", "slug"],
-  "115": ["n01950731", "sea_slug"],
-  "116": ["n01955084", "chiton"],
-  "117": ["n01968897", "chambered_nautilus"],
-  "118": ["n01978287", "Dungeness_crab"],
-  "119": ["n01978455", "rock_crab"],
-  "120": ["n01980166", "fiddler_crab"],
-  "121": ["n01981276", "king_crab"],
-  "122": ["n01983481", "American_lobster"],
-  "123": ["n01984695", "spiny_lobster"],
-  "124": ["n01985128", "crayfish"],
-  "125": ["n01986214", "hermit_crab"],
-  "126": ["n01990800", "isopod"],
-  "127": ["n02002556", "white_stork"],
-  "128": ["n02002724", "black_stork"],
-  "129": ["n02006656", "spoonbill"],
-  "130": ["n02007558", "flamingo"],
-  "131": ["n02009229", "little_blue_heron"],
-  "132": ["n02009912", "American_egret"],
-  "133": ["n02011460", "bittern"],
-  "134": ["n02012849", "crane"],
-  "135": ["n02013706", "limpkin"],
-  "136": ["n02017213", "European_gallinule"],
-  "137": ["n02018207", "American_coot"],
-  "138": ["n02018795", "bustard"],
-  "139": ["n02025239", "ruddy_turnstone"],
-  "140": ["n02027492", "red-backed_sandpiper"],
-  "141": ["n02028035", "redshank"],
-  "142": ["n02033041", "dowitcher"],
-  "143": ["n02037110", "oystercatcher"],
-  "144": ["n02051845", "pelican"],
-  "145": ["n02056570", "king_penguin"],
-  "146": ["n02058221", "albatross"],
-  "147": ["n02066245", "grey_whale"],
-  "148": ["n02071294", "killer_whale"],
-  "149": ["n02074367", "dugong"],
-  "150": ["n02077923", "sea_lion"],
-  "151": ["n02085620", "Chihuahua"],
-  "152": ["n02085782", "Japanese_spaniel"],
-  "153": ["n02085936", "Maltese_dog"],
-  "154": ["n02086079", "Pekinese"],
-  "155": ["n02086240", "Shih-Tzu"],
-  "156": ["n02086646", "Blenheim_spaniel"],
-  "157": ["n02086910", "papillon"],
-  "158": ["n02087046", "toy_terrier"],
-  "159": ["n02087394", "Rhodesian_ridgeback"],
-  "160": ["n02088094", "Afghan_hound"],
-  "161": ["n02088238", "basset"],
-  "162": ["n02088364", "beagle"],
-  "163": ["n02088466", "bloodhound"],
-  "164": ["n02088632", "bluetick"],
-  "165": ["n02089078", "black-and-tan_coonhound"],
-  "166": ["n02089867", "Walker_hound"],
-  "167": ["n02089973", "English_foxhound"],
-  "168": ["n02090379", "redbone"],
-  "169": ["n02090622", "borzoi"],
-  "170": ["n02090721", "Irish_wolfhound"],
-  "171": ["n02091032", "Italian_greyhound"],
-  "172": ["n02091134", "whippet"],
-  "173": ["n02091244", "Ibizan_hound"],
-  "174": ["n02091467", "Norwegian_elkhound"],
-  "175": ["n02091635", "otterhound"],
-  "176": ["n02091831", "Saluki"],
-  "177": ["n02092002", "Scottish_deerhound"],
-  "178": ["n02092339", "Weimaraner"],
-  "179": ["n02093256", "Staffordshire_bullterrier"],
-  "180": ["n02093428", "American_Staffordshire_terrier"],
-  "181": ["n02093647", "Bedlington_terrier"],
-  "182": ["n02093754", "Border_terrier"],
-  "183": ["n02093859", "Kerry_blue_terrier"],
-  "184": ["n02093991", "Irish_terrier"],
-  "185": ["n02094114", "Norfolk_terrier"],
-  "186": ["n02094258", "Norwich_terrier"],
-  "187": ["n02094433", "Yorkshire_terrier"],
-  "188": ["n02095314", "wire-haired_fox_terrier"],
-  "189": ["n02095570", "Lakeland_terrier"],
-  "190": ["n02095889", "Sealyham_terrier"],
-  "191": ["n02096051", "Airedale"],
-  "192": ["n02096177", "cairn"],
-  "193": ["n02096294", "Australian_terrier"],
-  "194": ["n02096437", "Dandie_Dinmont"],
-  "195": ["n02096585", "Boston_bull"],
-  "196": ["n02097047", "miniature_schnauzer"],
-  "197": ["n02097130", "giant_schnauzer"],
-  "198": ["n02097209", "standard_schnauzer"],
-  "199": ["n02097298", "Scotch_terrier"],
-  "200": ["n02097474", "Tibetan_terrier"],
-  "201": ["n02097658", "silky_terrier"],
-  "202": ["n02098105", "soft-coated_wheaten_terrier"],
-  "203": ["n02098286", "West_Highland_white_terrier"],
-  "204": ["n02098413", "Lhasa"],
-  "205": ["n02099267", "flat-coated_retriever"],
-  "206": ["n02099429", "curly-coated_retriever"],
-  "207": ["n02099601", "golden_retriever"],
-  "208": ["n02099712", "Labrador_retriever"],
-  "209": ["n02099849", "Chesapeake_Bay_retriever"],
-  "210": ["n02100236", "German_short-haired_pointer"],
-  "211": ["n02100583", "vizsla"],
-  "212": ["n02100735", "English_setter"],
-  "213": ["n02100877", "Irish_setter"],
-  "214": ["n02101006", "Gordon_setter"],
-  "215": ["n02101388", "Brittany_spaniel"],
-  "216": ["n02101556", "clumber"],
-  "217": ["n02102040", "English_springer"],
-  "218": ["n02102177", "Welsh_springer_spaniel"],
-  "219": ["n02102318", "cocker_spaniel"],
-  "220": ["n02102480", "Sussex_spaniel"],
-  "221": ["n02102973", "Irish_water_spaniel"],
-  "222": ["n02104029", "kuvasz"],
-  "223": ["n02104365", "schipperke"],
-  "224": ["n02105056", "groenendael"],
-  "225": ["n02105162", "malinois"],
-  "226": ["n02105251", "briard"],
-  "227": ["n02105412", "kelpie"],
-  "228": ["n02105505", "komondor"],
-  "229": ["n02105641", "Old_English_sheepdog"],
-  "230": ["n02105855", "Shetland_sheepdog"],
-  "231": ["n02106030", "collie"],
-  "232": ["n02106166", "Border_collie"],
-  "233": ["n02106382", "Bouvier_des_Flandres"],
-  "234": ["n02106550", "Rottweiler"],
-  "235": ["n02106662", "German_shepherd"],
-  "236": ["n02107142", "Doberman"],
-  "237": ["n02107312", "miniature_pinscher"],
-  "238": ["n02107574", "Greater_Swiss_Mountain_dog"],
-  "239": ["n02107683", "Bernese_mountain_dog"],
-  "240": ["n02107908", "Appenzeller"],
-  "241": ["n02108000", "EntleBucher"],
-  "242": ["n02108089", "boxer"],
-  "243": ["n02108422", "bull_mastiff"],
-  "244": ["n02108551", "Tibetan_mastiff"],
-  "245": ["n02108915", "French_bulldog"],
-  "246": ["n02109047", "Great_Dane"],
-  "247": ["n02109525", "Saint_Bernard"],
-  "248": ["n02109961", "Eskimo_dog"],
-  "249": ["n02110063", "malamute"],
-  "250": ["n02110185", "Siberian_husky"],
-  "251": ["n02110341", "dalmatian"],
-  "252": ["n02110627", "affenpinscher"],
-  "253": ["n02110806", "basenji"],
-  "254": ["n02110958", "pug"],
-  "255": ["n02111129", "Leonberg"],
-  "256": ["n02111277", "Newfoundland"],
-  "257": ["n02111500", "Great_Pyrenees"],
-  "258": ["n02111889", "Samoyed"],
-  "259": ["n02112018", "Pomeranian"],
-  "260": ["n02112137", "chow"],
-  "261": ["n02112350", "keeshond"],
-  "262": ["n02112706", "Brabancon_griffon"],
-  "263": ["n02113023", "Pembroke"],
-  "264": ["n02113186", "Cardigan"],
-  "265": ["n02113624", "toy_poodle"],
-  "266": ["n02113712", "miniature_poodle"],
-  "267": ["n02113799", "standard_poodle"],
-  "268": ["n02113978", "Mexican_hairless"],
-  "269": ["n02114367", "timber_wolf"],
-  "270": ["n02114548", "white_wolf"],
-  "271": ["n02114712", "red_wolf"],
-  "272": ["n02114855", "coyote"],
-  "273": ["n02115641", "dingo"],
-  "274": ["n02115913", "dhole"],
-  "275": ["n02116738", "African_hunting_dog"],
-  "276": ["n02117135", "hyena"],
-  "277": ["n02119022", "red_fox"],
-  "278": ["n02119789", "kit_fox"],
-  "279": ["n02120079", "Arctic_fox"],
-  "280": ["n02120505", "grey_fox"],
-  "281": ["n02123045", "tabby"],
-  "282": ["n02123159", "tiger_cat"],
-  "283": ["n02123394", "Persian_cat"],
-  "284": ["n02123597", "Siamese_cat"],
-  "285": ["n02124075", "Egyptian_cat"],
-  "286": ["n02125311", "cougar"],
-  "287": ["n02127052", "lynx"],
-  "288": ["n02128385", "leopard"],
-  "289": ["n02128757", "snow_leopard"],
-  "290": ["n02128925", "jaguar"],
-  "291": ["n02129165", "lion"],
-  "292": ["n02129604", "tiger"],
-  "293": ["n02130308", "cheetah"],
-  "294": ["n02132136", "brown_bear"],
-  "295": ["n02133161", "American_black_bear"],
-  "296": ["n02134084", "ice_bear"],
-  "297": ["n02134418", "sloth_bear"],
-  "298": ["n02137549", "mongoose"],
-  "299": ["n02138441", "meerkat"],
-  "300": ["n02165105", "tiger_beetle"],
-  "301": ["n02165456", "ladybug"],
-  "302": ["n02167151", "ground_beetle"],
-  "303": ["n02168699", "long-horned_beetle"],
-  "304": ["n02169497", "leaf_beetle"],
-  "305": ["n02172182", "dung_beetle"],
-  "306": ["n02174001", "rhinoceros_beetle"],
-  "307": ["n02177972", "weevil"],
-  "308": ["n02190166", "fly"],
-  "309": ["n02206856", "bee"],
-  "310": ["n02219486", "ant"],
-  "311": ["n02226429", "grasshopper"],
-  "312": ["n02229544", "cricket"],
-  "313": ["n02231487", "walking_stick"],
-  "314": ["n02233338", "cockroach"],
-  "315": ["n02236044", "mantis"],
-  "316": ["n02256656", "cicada"],
-  "317": ["n02259212", "leafhopper"],
-  "318": ["n02264363", "lacewing"],
-  "319": ["n02268443", "dragonfly"],
-  "320": ["n02268853", "damselfly"],
-  "321": ["n02276258", "admiral"],
-  "322": ["n02277742", "ringlet"],
-  "323": ["n02279972", "monarch"],
-  "324": ["n02280649", "cabbage_butterfly"],
-  "325": ["n02281406", "sulphur_butterfly"],
-  "326": ["n02281787", "lycaenid"],
-  "327": ["n02317335", "starfish"],
-  "328": ["n02319095", "sea_urchin"],
-  "329": ["n02321529", "sea_cucumber"],
-  "330": ["n02325366", "wood_rabbit"],
-  "331": ["n02326432", "hare"],
-  "332": ["n02328150", "Angora"],
-  "333": ["n02342885", "hamster"],
-  "334": ["n02346627", "porcupine"],
-  "335": ["n02356798", "fox_squirrel"],
-  "336": ["n02361337", "marmot"],
-  "337": ["n02363005", "beaver"],
-  "338": ["n02364673", "guinea_pig"],
-  "339": ["n02389026", "sorrel"],
-  "340": ["n02391049", "zebra"],
-  "341": ["n02395406", "hog"],
-  "342": ["n02396427", "wild_boar"],
-  "343": ["n02397096", "warthog"],
-  "344": ["n02398521", "hippopotamus"],
-  "345": ["n02403003", "ox"],
-  "346": ["n02408429", "water_buffalo"],
-  "347": ["n02410509", "bison"],
-  "348": ["n02412080", "ram"],
-  "349": ["n02415577", "bighorn"],
-  "350": ["n02417914", "ibex"],
-  "351": ["n02422106", "hartebeest"],
-  "352": ["n02422699", "impala"],
-  "353": ["n02423022", "gazelle"],
-  "354": ["n02437312", "Arabian_camel"],
-  "355": ["n02437616", "llama"],
-  "356": ["n02441942", "weasel"],
-  "357": ["n02442845", "mink"],
-  "358": ["n02443114", "polecat"],
-  "359": ["n02443484", "black-footed_ferret"],
-  "360": ["n02444819", "otter"],
-  "361": ["n02445715", "skunk"],
-  "362": ["n02447366", "badger"],
-  "363": ["n02454379", "armadillo"],
-  "364": ["n02457408", "three-toed_sloth"],
-  "365": ["n02480495", "orangutan"],
-  "366": ["n02480855", "gorilla"],
-  "367": ["n02481823", "chimpanzee"],
-  "368": ["n02483362", "gibbon"],
-  "369": ["n02483708", "siamang"],
-  "370": ["n02484975", "guenon"],
-  "371": ["n02486261", "patas"],
-  "372": ["n02486410", "baboon"],
-  "373": ["n02487347", "macaque"],
-  "374": ["n02488291", "langur"],
-  "375": ["n02488702", "colobus"],
-  "376": ["n02489166", "proboscis_monkey"],
-  "377": ["n02490219", "marmoset"],
-  "378": ["n02492035", "capuchin"],
-  "379": ["n02492660", "howler_monkey"],
-  "380": ["n02493509", "titi"],
-  "381": ["n02493793", "spider_monkey"],
-  "382": ["n02494079", "squirrel_monkey"],
-  "383": ["n02497673", "Madagascar_cat"],
-  "384": ["n02500267", "indri"],
-  "385": ["n02504013", "Indian_elephant"],
-  "386": ["n02504458", "African_elephant"],
-  "387": ["n02509815", "lesser_panda"],
-  "388": ["n02510455", "giant_panda"],
-  "389": ["n02514041", "barracouta"],
-  "390": ["n02526121", "eel"],
-  "391": ["n02536864", "coho"],
-  "392": ["n02606052", "rock_beauty"],
-  "393": ["n02607072", "anemone_fish"],
-  "394": ["n02640242", "sturgeon"],
-  "395": ["n02641379", "gar"],
-  "396": ["n02643566", "lionfish"],
-  "397": ["n02655020", "puffer"],
-  "398": ["n02666196", "abacus"],
-  "399": ["n02667093", "abaya"],
-  "400": ["n02669723", "academic_gown"],
-  "401": ["n02672831", "accordion"],
-  "402": ["n02676566", "acoustic_guitar"],
-  "403": ["n02687172", "aircraft_carrier"],
-  "404": ["n02690373", "airliner"],
-  "405": ["n02692877", "airship"],
-  "406": ["n02699494", "altar"],
-  "407": ["n02701002", "ambulance"],
-  "408": ["n02704792", "amphibian"],
-  "409": ["n02708093", "analog_clock"],
-  "410": ["n02727426", "apiary"],
-  "411": ["n02730930", "apron"],
-  "412": ["n02747177", "ashcan"],
-  "413": ["n02749479", "assault_rifle"],
-  "414": ["n02769748", "backpack"],
-  "415": ["n02776631", "bakery"],
-  "416": ["n02777292", "balance_beam"],
-  "417": ["n02782093", "balloon"],
-  "418": ["n02783161", "ballpoint"],
-  "419": ["n02786058", "Band_Aid"],
-  "420": ["n02787622", "banjo"],
-  "421": ["n02788148", "bannister"],
-  "422": ["n02790996", "barbell"],
-  "423": ["n02791124", "barber_chair"],
-  "424": ["n02791270", "barbershop"],
-  "425": ["n02793495", "barn"],
-  "426": ["n02794156", "barometer"],
-  "427": ["n02795169", "barrel"],
-  "428": ["n02797295", "barrow"],
-  "429": ["n02799071", "baseball"],
-  "430": ["n02802426", "basketball"],
-  "431": ["n02804414", "bassinet"],
-  "432": ["n02804610", "bassoon"],
-  "433": ["n02807133", "bathing_cap"],
-  "434": ["n02808304", "bath_towel"],
-  "435": ["n02808440", "bathtub"],
-  "436": ["n02814533", "beach_wagon"],
-  "437": ["n02814860", "beacon"],
-  "438": ["n02815834", "beaker"],
-  "439": ["n02817516", "bearskin"],
-  "440": ["n02823428", "beer_bottle"],
-  "441": ["n02823750", "beer_glass"],
-  "442": ["n02825657", "bell_cote"],
-  "443": ["n02834397", "bib"],
-  "444": ["n02835271", "bicycle-built-for-two"],
-  "445": ["n02837789", "bikini"],
-  "446": ["n02840245", "binder"],
-  "447": ["n02841315", "binoculars"],
-  "448": ["n02843684", "birdhouse"],
-  "449": ["n02859443", "boathouse"],
-  "450": ["n02860847", "bobsled"],
-  "451": ["n02865351", "bolo_tie"],
-  "452": ["n02869837", "bonnet"],
-  "453": ["n02870880", "bookcase"],
-  "454": ["n02871525", "bookshop"],
-  "455": ["n02877765", "bottlecap"],
-  "456": ["n02879718", "bow"],
-  "457": ["n02883205", "bow_tie"],
-  "458": ["n02892201", "brass"],
-  "459": ["n02892767", "brassiere"],
-  "460": ["n02894605", "breakwater"],
-  "461": ["n02895154", "breastplate"],
-  "462": ["n02906734", "broom"],
-  "463": ["n02909870", "bucket"],
-  "464": ["n02910353", "buckle"],
-  "465": ["n02916936", "bulletproof_vest"],
-  "466": ["n02917067", "bullet_train"],
-  "467": ["n02927161", "butcher_shop"],
-  "468": ["n02930766", "cab"],
-  "469": ["n02939185", "caldron"],
-  "470": ["n02948072", "candle"],
-  "471": ["n02950826", "cannon"],
-  "472": ["n02951358", "canoe"],
-  "473": ["n02951585", "can_opener"],
-  "474": ["n02963159", "cardigan"],
-  "475": ["n02965783", "car_mirror"],
-  "476": ["n02966193", "carousel"],
-  "477": ["n02966687", "carpenter's_kit"],
-  "478": ["n02971356", "carton"],
-  "479": ["n02974003", "car_wheel"],
-  "480": ["n02977058", "cash_machine"],
-  "481": ["n02978881", "cassette"],
-  "482": ["n02979186", "cassette_player"],
-  "483": ["n02980441", "castle"],
-  "484": ["n02981792", "catamaran"],
-  "485": ["n02988304", "CD_player"],
-  "486": ["n02992211", "cello"],
-  "487": ["n02992529", "cellular_telephone"],
-  "488": ["n02999410", "chain"],
-  "489": ["n03000134", "chainlink_fence"],
-  "490": ["n03000247", "chain_mail"],
-  "491": ["n03000684", "chain_saw"],
-  "492": ["n03014705", "chest"],
-  "493": ["n03016953", "chiffonier"],
-  "494": ["n03017168", "chime"],
-  "495": ["n03018349", "china_cabinet"],
-  "496": ["n03026506", "Christmas_stocking"],
-  "497": ["n03028079", "church"],
-  "498": ["n03032252", "cinema"],
-  "499": ["n03041632", "cleaver"],
-  "500": ["n03042490", "cliff_dwelling"],
-  "501": ["n03045698", "cloak"],
-  "502": ["n03047690", "clog"],
-  "503": ["n03062245", "cocktail_shaker"],
-  "504": ["n03063599", "coffee_mug"],
-  "505": ["n03063689", "coffeepot"],
-  "506": ["n03065424", "coil"],
-  "507": ["n03075370", "combination_lock"],
-  "508": ["n03085013", "computer_keyboard"],
-  "509": ["n03089624", "confectionery"],
-  "510": ["n03095699", "container_ship"],
-  "511": ["n03100240", "convertible"],
-  "512": ["n03109150", "corkscrew"],
-  "513": ["n03110669", "cornet"],
-  "514": ["n03124043", "cowboy_boot"],
-  "515": ["n03124170", "cowboy_hat"],
-  "516": ["n03125729", "cradle"],
-  "517": ["n03126707", "crane"],
-  "518": ["n03127747", "crash_helmet"],
-  "519": ["n03127925", "crate"],
-  "520": ["n03131574", "crib"],
-  "521": ["n03133878", "Crock_Pot"],
-  "522": ["n03134739", "croquet_ball"],
-  "523": ["n03141823", "crutch"],
-  "524": ["n03146219", "cuirass"],
-  "525": ["n03160309", "dam"],
-  "526": ["n03179701", "desk"],
-  "527": ["n03180011", "desktop_computer"],
-  "528": ["n03187595", "dial_telephone"],
-  "529": ["n03188531", "diaper"],
-  "530": ["n03196217", "digital_clock"],
-  "531": ["n03197337", "digital_watch"],
-  "532": ["n03201208", "dining_table"],
-  "533": ["n03207743", "dishrag"],
-  "534": ["n03207941", "dishwasher"],
-  "535": ["n03208938", "disk_brake"],
-  "536": ["n03216828", "dock"],
-  "537": ["n03218198", "dogsled"],
-  "538": ["n03220513", "dome"],
-  "539": ["n03223299", "doormat"],
-  "540": ["n03240683", "drilling_platform"],
-  "541": ["n03249569", "drum"],
-  "542": ["n03250847", "drumstick"],
-  "543": ["n03255030", "dumbbell"],
-  "544": ["n03259280", "Dutch_oven"],
-  "545": ["n03271574", "electric_fan"],
-  "546": ["n03272010", "electric_guitar"],
-  "547": ["n03272562", "electric_locomotive"],
-  "548": ["n03290653", "entertainment_center"],
-  "549": ["n03291819", "envelope"],
-  "550": ["n03297495", "espresso_maker"],
-  "551": ["n03314780", "face_powder"],
-  "552": ["n03325584", "feather_boa"],
-  "553": ["n03337140", "file"],
-  "554": ["n03344393", "fireboat"],
-  "555": ["n03345487", "fire_engine"],
-  "556": ["n03347037", "fire_screen"],
-  "557": ["n03355925", "flagpole"],
-  "558": ["n03372029", "flute"],
-  "559": ["n03376595", "folding_chair"],
-  "560": ["n03379051", "football_helmet"],
-  "561": ["n03384352", "forklift"],
-  "562": ["n03388043", "fountain"],
-  "563": ["n03388183", "fountain_pen"],
-  "564": ["n03388549", "four-poster"],
-  "565": ["n03393912", "freight_car"],
-  "566": ["n03394916", "French_horn"],
-  "567": ["n03400231", "frying_pan"],
-  "568": ["n03404251", "fur_coat"],
-  "569": ["n03417042", "garbage_truck"],
-  "570": ["n03424325", "gasmask"],
-  "571": ["n03425413", "gas_pump"],
-  "572": ["n03443371", "goblet"],
-  "573": ["n03444034", "go-kart"],
-  "574": ["n03445777", "golf_ball"],
-  "575": ["n03445924", "golfcart"],
-  "576": ["n03447447", "gondola"],
-  "577": ["n03447721", "gong"],
-  "578": ["n03450230", "gown"],
-  "579": ["n03452741", "grand_piano"],
-  "580": ["n03457902", "greenhouse"],
-  "581": ["n03459775", "grille"],
-  "582": ["n03461385", "grocery_store"],
-  "583": ["n03467068", "guillotine"],
-  "584": ["n03476684", "hair_slide"],
-  "585": ["n03476991", "hair_spray"],
-  "586": ["n03478589", "half_track"],
-  "587": ["n03481172", "hammer"],
-  "588": ["n03482405", "hamper"],
-  "589": ["n03483316", "hand_blower"],
-  "590": ["n03485407", "hand-held_computer"],
-  "591": ["n03485794", "handkerchief"],
-  "592": ["n03492542", "hard_disc"],
-  "593": ["n03494278", "harmonica"],
-  "594": ["n03495258", "harp"],
-  "595": ["n03496892", "harvester"],
-  "596": ["n03498962", "hatchet"],
-  "597": ["n03527444", "holster"],
-  "598": ["n03529860", "home_theater"],
-  "599": ["n03530642", "honeycomb"],
-  "600": ["n03532672", "hook"],
-  "601": ["n03534580", "hoopskirt"],
-  "602": ["n03535780", "horizontal_bar"],
-  "603": ["n03538406", "horse_cart"],
-  "604": ["n03544143", "hourglass"],
-  "605": ["n03584254", "iPod"],
-  "606": ["n03584829", "iron"],
-  "607": ["n03590841", "jack-o'-lantern"],
-  "608": ["n03594734", "jean"],
-  "609": ["n03594945", "jeep"],
-  "610": ["n03595614", "jersey"],
-  "611": ["n03598930", "jigsaw_puzzle"],
-  "612": ["n03599486", "jinrikisha"],
-  "613": ["n03602883", "joystick"],
-  "614": ["n03617480", "kimono"],
-  "615": ["n03623198", "knee_pad"],
-  "616": ["n03627232", "knot"],
-  "617": ["n03630383", "lab_coat"],
-  "618": ["n03633091", "ladle"],
-  "619": ["n03637318", "lampshade"],
-  "620": ["n03642806", "laptop"],
-  "621": ["n03649909", "lawn_mower"],
-  "622": ["n03657121", "lens_cap"],
-  "623": ["n03658185", "letter_opener"],
-  "624": ["n03661043", "library"],
-  "625": ["n03662601", "lifeboat"],
-  "626": ["n03666591", "lighter"],
-  "627": ["n03670208", "limousine"],
-  "628": ["n03673027", "liner"],
-  "629": ["n03676483", "lipstick"],
-  "630": ["n03680355", "Loafer"],
-  "631": ["n03690938", "lotion"],
-  "632": ["n03691459", "loudspeaker"],
-  "633": ["n03692522", "loupe"],
-  "634": ["n03697007", "lumbermill"],
-  "635": ["n03706229", "magnetic_compass"],
-  "636": ["n03709823", "mailbag"],
-  "637": ["n03710193", "mailbox"],
-  "638": ["n03710637", "maillot"],
-  "639": ["n03710721", "maillot"],
-  "640": ["n03717622", "manhole_cover"],
-  "641": ["n03720891", "maraca"],
-  "642": ["n03721384", "marimba"],
-  "643": ["n03724870", "mask"],
-  "644": ["n03729826", "matchstick"],
-  "645": ["n03733131", "maypole"],
-  "646": ["n03733281", "maze"],
-  "647": ["n03733805", "measuring_cup"],
-  "648": ["n03742115", "medicine_chest"],
-  "649": ["n03743016", "megalith"],
-  "650": ["n03759954", "microphone"],
-  "651": ["n03761084", "microwave"],
-  "652": ["n03763968", "military_uniform"],
-  "653": ["n03764736", "milk_can"],
-  "654": ["n03769881", "minibus"],
-  "655": ["n03770439", "miniskirt"],
-  "656": ["n03770679", "minivan"],
-  "657": ["n03773504", "missile"],
-  "658": ["n03775071", "mitten"],
-  "659": ["n03775546", "mixing_bowl"],
-  "660": ["n03776460", "mobile_home"],
-  "661": ["n03777568", "Model_T"],
-  "662": ["n03777754", "modem"],
-  "663": ["n03781244", "monastery"],
-  "664": ["n03782006", "monitor"],
-  "665": ["n03785016", "moped"],
-  "666": ["n03786901", "mortar"],
-  "667": ["n03787032", "mortarboard"],
-  "668": ["n03788195", "mosque"],
-  "669": ["n03788365", "mosquito_net"],
-  "670": ["n03791053", "motor_scooter"],
-  "671": ["n03792782", "mountain_bike"],
-  "672": ["n03792972", "mountain_tent"],
-  "673": ["n03793489", "mouse"],
-  "674": ["n03794056", "mousetrap"],
-  "675": ["n03796401", "moving_van"],
-  "676": ["n03803284", "muzzle"],
-  "677": ["n03804744", "nail"],
-  "678": ["n03814639", "neck_brace"],
-  "679": ["n03814906", "necklace"],
-  "680": ["n03825788", "nipple"],
-  "681": ["n03832673", "notebook"],
-  "682": ["n03837869", "obelisk"],
-  "683": ["n03838899", "oboe"],
-  "684": ["n03840681", "ocarina"],
-  "685": ["n03841143", "odometer"],
-  "686": ["n03843555", "oil_filter"],
-  "687": ["n03854065", "organ"],
-  "688": ["n03857828", "oscilloscope"],
-  "689": ["n03866082", "overskirt"],
-  "690": ["n03868242", "oxcart"],
-  "691": ["n03868863", "oxygen_mask"],
-  "692": ["n03871628", "packet"],
-  "693": ["n03873416", "paddle"],
-  "694": ["n03874293", "paddlewheel"],
-  "695": ["n03874599", "padlock"],
-  "696": ["n03876231", "paintbrush"],
-  "697": ["n03877472", "pajama"],
-  "698": ["n03877845", "palace"],
-  "699": ["n03884397", "panpipe"],
-  "700": ["n03887697", "paper_towel"],
-  "701": ["n03888257", "parachute"],
-  "702": ["n03888605", "parallel_bars"],
-  "703": ["n03891251", "park_bench"],
-  "704": ["n03891332", "parking_meter"],
-  "705": ["n03895866", "passenger_car"],
-  "706": ["n03899768", "patio"],
-  "707": ["n03902125", "pay-phone"],
-  "708": ["n03903868", "pedestal"],
-  "709": ["n03908618", "pencil_box"],
-  "710": ["n03908714", "pencil_sharpener"],
-  "711": ["n03916031", "perfume"],
-  "712": ["n03920288", "Petri_dish"],
-  "713": ["n03924679", "photocopier"],
-  "714": ["n03929660", "pick"],
-  "715": ["n03929855", "pickelhaube"],
-  "716": ["n03930313", "picket_fence"],
-  "717": ["n03930630", "pickup"],
-  "718": ["n03933933", "pier"],
-  "719": ["n03935335", "piggy_bank"],
-  "720": ["n03937543", "pill_bottle"],
-  "721": ["n03938244", "pillow"],
-  "722": ["n03942813", "ping-pong_ball"],
-  "723": ["n03944341", "pinwheel"],
-  "724": ["n03947888", "pirate"],
-  "725": ["n03950228", "pitcher"],
-  "726": ["n03954731", "plane"],
-  "727": ["n03956157", "planetarium"],
-  "728": ["n03958227", "plastic_bag"],
-  "729": ["n03961711", "plate_rack"],
-  "730": ["n03967562", "plow"],
-  "731": ["n03970156", "plunger"],
-  "732": ["n03976467", "Polaroid_camera"],
-  "733": ["n03976657", "pole"],
-  "734": ["n03977966", "police_van"],
-  "735": ["n03980874", "poncho"],
-  "736": ["n03982430", "pool_table"],
-  "737": ["n03983396", "pop_bottle"],
-  "738": ["n03991062", "pot"],
-  "739": ["n03992509", "potter's_wheel"],
-  "740": ["n03995372", "power_drill"],
-  "741": ["n03998194", "prayer_rug"],
-  "742": ["n04004767", "printer"],
-  "743": ["n04005630", "prison"],
-  "744": ["n04008634", "projectile"],
-  "745": ["n04009552", "projector"],
-  "746": ["n04019541", "puck"],
-  "747": ["n04023962", "punching_bag"],
-  "748": ["n04026417", "purse"],
-  "749": ["n04033901", "quill"],
-  "750": ["n04033995", "quilt"],
-  "751": ["n04037443", "racer"],
-  "752": ["n04039381", "racket"],
-  "753": ["n04040759", "radiator"],
-  "754": ["n04041544", "radio"],
-  "755": ["n04044716", "radio_telescope"],
-  "756": ["n04049303", "rain_barrel"],
-  "757": ["n04065272", "recreational_vehicle"],
-  "758": ["n04067472", "reel"],
-  "759": ["n04069434", "reflex_camera"],
-  "760": ["n04070727", "refrigerator"],
-  "761": ["n04074963", "remote_control"],
-  "762": ["n04081281", "restaurant"],
-  "763": ["n04086273", "revolver"],
-  "764": ["n04090263", "rifle"],
-  "765": ["n04099969", "rocking_chair"],
-  "766": ["n04111531", "rotisserie"],
-  "767": ["n04116512", "rubber_eraser"],
-  "768": ["n04118538", "rugby_ball"],
-  "769": ["n04118776", "rule"],
-  "770": ["n04120489", "running_shoe"],
-  "771": ["n04125021", "safe"],
-  "772": ["n04127249", "safety_pin"],
-  "773": ["n04131690", "saltshaker"],
-  "774": ["n04133789", "sandal"],
-  "775": ["n04136333", "sarong"],
-  "776": ["n04141076", "sax"],
-  "777": ["n04141327", "scabbard"],
-  "778": ["n04141975", "scale"],
-  "779": ["n04146614", "school_bus"],
-  "780": ["n04147183", "schooner"],
-  "781": ["n04149813", "scoreboard"],
-  "782": ["n04152593", "screen"],
-  "783": ["n04153751", "screw"],
-  "784": ["n04154565", "screwdriver"],
-  "785": ["n04162706", "seat_belt"],
-  "786": ["n04179913", "sewing_machine"],
-  "787": ["n04192698", "shield"],
-  "788": ["n04200800", "shoe_shop"],
-  "789": ["n04201297", "shoji"],
-  "790": ["n04204238", "shopping_basket"],
-  "791": ["n04204347", "shopping_cart"],
-  "792": ["n04208210", "shovel"],
-  "793": ["n04209133", "shower_cap"],
-  "794": ["n04209239", "shower_curtain"],
-  "795": ["n04228054", "ski"],
-  "796": ["n04229816", "ski_mask"],
-  "797": ["n04235860", "sleeping_bag"],
-  "798": ["n04238763", "slide_rule"],
-  "799": ["n04239074", "sliding_door"],
-  "800": ["n04243546", "slot"],
-  "801": ["n04251144", "snorkel"],
-  "802": ["n04252077", "snowmobile"],
-  "803": ["n04252225", "snowplow"],
-  "804": ["n04254120", "soap_dispenser"],
-  "805": ["n04254680", "soccer_ball"],
-  "806": ["n04254777", "sock"],
-  "807": ["n04258138", "solar_dish"],
-  "808": ["n04259630", "sombrero"],
-  "809": ["n04263257", "soup_bowl"],
-  "810": ["n04264628", "space_bar"],
-  "811": ["n04265275", "space_heater"],
-  "812": ["n04266014", "space_shuttle"],
-  "813": ["n04270147", "spatula"],
-  "814": ["n04273569", "speedboat"],
-  "815": ["n04275548", "spider_web"],
-  "816": ["n04277352", "spindle"],
-  "817": ["n04285008", "sports_car"],
-  "818": ["n04286575", "spotlight"],
-  "819": ["n04296562", "stage"],
-  "820": ["n04310018", "steam_locomotive"],
-  "821": ["n04311004", "steel_arch_bridge"],
-  "822": ["n04311174", "steel_drum"],
-  "823": ["n04317175", "stethoscope"],
-  "824": ["n04325704", "stole"],
-  "825": ["n04326547", "stone_wall"],
-  "826": ["n04328186", "stopwatch"],
-  "827": ["n04330267", "stove"],
-  "828": ["n04332243", "strainer"],
-  "829": ["n04335435", "streetcar"],
-  "830": ["n04336792", "stretcher"],
-  "831": ["n04344873", "studio_couch"],
-  "832": ["n04346328", "stupa"],
-  "833": ["n04347754", "submarine"],
-  "834": ["n04350905", "suit"],
-  "835": ["n04355338", "sundial"],
-  "836": ["n04355933", "sunglass"],
-  "837": ["n04356056", "sunglasses"],
-  "838": ["n04357314", "sunscreen"],
-  "839": ["n04366367", "suspension_bridge"],
-  "840": ["n04367480", "swab"],
-  "841": ["n04370456", "sweatshirt"],
-  "842": ["n04371430", "swimming_trunks"],
-  "843": ["n04371774", "swing"],
-  "844": ["n04372370", "switch"],
-  "845": ["n04376876", "syringe"],
-  "846": ["n04380533", "table_lamp"],
-  "847": ["n04389033", "tank"],
-  "848": ["n04392985", "tape_player"],
-  "849": ["n04398044", "teapot"],
-  "850": ["n04399382", "teddy"],
-  "851": ["n04404412", "television"],
-  "852": ["n04409515", "tennis_ball"],
-  "853": ["n04417672", "thatch"],
-  "854": ["n04418357", "theater_curtain"],
-  "855": ["n04423845", "thimble"],
-  "856": ["n04428191", "thresher"],
-  "857": ["n04429376", "throne"],
-  "858": ["n04435653", "tile_roof"],
-  "859": ["n04442312", "toaster"],
-  "860": ["n04443257", "tobacco_shop"],
-  "861": ["n04447861", "toilet_seat"],
-  "862": ["n04456115", "torch"],
-  "863": ["n04458633", "totem_pole"],
-  "864": ["n04461696", "tow_truck"],
-  "865": ["n04462240", "toyshop"],
-  "866": ["n04465501", "tractor"],
-  "867": ["n04467665", "trailer_truck"],
-  "868": ["n04476259", "tray"],
-  "869": ["n04479046", "trench_coat"],
-  "870": ["n04482393", "tricycle"],
-  "871": ["n04483307", "trimaran"],
-  "872": ["n04485082", "tripod"],
-  "873": ["n04486054", "triumphal_arch"],
-  "874": ["n04487081", "trolleybus"],
-  "875": ["n04487394", "trombone"],
-  "876": ["n04493381", "tub"],
-  "877": ["n04501370", "turnstile"],
-  "878": ["n04505470", "typewriter_keyboard"],
-  "879": ["n04507155", "umbrella"],
-  "880": ["n04509417", "unicycle"],
-  "881": ["n04515003", "upright"],
-  "882": ["n04517823", "vacuum"],
-  "883": ["n04522168", "vase"],
-  "884": ["n04523525", "vault"],
-  "885": ["n04525038", "velvet"],
-  "886": ["n04525305", "vending_machine"],
-  "887": ["n04532106", "vestment"],
-  "888": ["n04532670", "viaduct"],
-  "889": ["n04536866", "violin"],
-  "890": ["n04540053", "volleyball"],
-  "891": ["n04542943", "waffle_iron"],
-  "892": ["n04548280", "wall_clock"],
-  "893": ["n04548362", "wallet"],
-  "894": ["n04550184", "wardrobe"],
-  "895": ["n04552348", "warplane"],
-  "896": ["n04553703", "washbasin"],
-  "897": ["n04554684", "washer"],
-  "898": ["n04557648", "water_bottle"],
-  "899": ["n04560804", "water_jug"],
-  "900": ["n04562935", "water_tower"],
-  "901": ["n04579145", "whiskey_jug"],
-  "902": ["n04579432", "whistle"],
-  "903": ["n04584207", "wig"],
-  "904": ["n04589890", "window_screen"],
-  "905": ["n04590129", "window_shade"],
-  "906": ["n04591157", "Windsor_tie"],
-  "907": ["n04591713", "wine_bottle"],
-  "908": ["n04592741", "wing"],
-  "909": ["n04596742", "wok"],
-  "910": ["n04597913", "wooden_spoon"],
-  "911": ["n04599235", "wool"],
-  "912": ["n04604644", "worm_fence"],
-  "913": ["n04606251", "wreck"],
-  "914": ["n04612504", "yawl"],
-  "915": ["n04613696", "yurt"],
-  "916": ["n06359193", "web_site"],
-  "917": ["n06596364", "comic_book"],
-  "918": ["n06785654", "crossword_puzzle"],
-  "919": ["n06794110", "street_sign"],
-  "920": ["n06874185", "traffic_light"],
-  "921": ["n07248320", "book_jacket"],
-  "922": ["n07565083", "menu"],
-  "923": ["n07579787", "plate"],
-  "924": ["n07583066", "guacamole"],
-  "925": ["n07584110", "consomme"],
-  "926": ["n07590611", "hot_pot"],
-  "927": ["n07613480", "trifle"],
-  "928": ["n07614500", "ice_cream"],
-  "929": ["n07615774", "ice_lolly"],
-  "930": ["n07684084", "French_loaf"],
-  "931": ["n07693725", "bagel"],
-  "932": ["n07695742", "pretzel"],
-  "933": ["n07697313", "cheeseburger"],
-  "934": ["n07697537", "hotdog"],
-  "935": ["n07711569", "mashed_potato"],
-  "936": ["n07714571", "head_cabbage"],
-  "937": ["n07714990", "broccoli"],
-  "938": ["n07715103", "cauliflower"],
-  "939": ["n07716358", "zucchini"],
-  "940": ["n07716906", "spaghetti_squash"],
-  "941": ["n07717410", "acorn_squash"],
-  "942": ["n07717556", "butternut_squash"],
-  "943": ["n07718472", "cucumber"],
-  "944": ["n07718747", "artichoke"],
-  "945": ["n07720875", "bell_pepper"],
-  "946": ["n07730033", "cardoon"],
-  "947": ["n07734744", "mushroom"],
-  "948": ["n07742313", "Granny_Smith"],
-  "949": ["n07745940", "strawberry"],
-  "950": ["n07747607", "orange"],
-  "951": ["n07749582", "lemon"],
-  "952": ["n07753113", "fig"],
-  "953": ["n07753275", "pineapple"],
-  "954": ["n07753592", "banana"],
-  "955": ["n07754684", "jackfruit"],
-  "956": ["n07760859", "custard_apple"],
-  "957": ["n07768694", "pomegranate"],
-  "958": ["n07802026", "hay"],
-  "959": ["n07831146", "carbonara"],
-  "960": ["n07836838", "chocolate_sauce"],
-  "961": ["n07860988", "dough"],
-  "962": ["n07871810", "meat_loaf"],
-  "963": ["n07873807", "pizza"],
-  "964": ["n07875152", "potpie"],
-  "965": ["n07880968", "burrito"],
-  "966": ["n07892512", "red_wine"],
-  "967": ["n07920052", "espresso"],
-  "968": ["n07930864", "cup"],
-  "969": ["n07932039", "eggnog"],
-  "970": ["n09193705", "alp"],
-  "971": ["n09229709", "bubble"],
-  "972": ["n09246464", "cliff"],
-  "973": ["n09256479", "coral_reef"],
-  "974": ["n09288635", "geyser"],
-  "975": ["n09332890", "lakeside"],
-  "976": ["n09399592", "promontory"],
-  "977": ["n09421951", "sandbar"],
-  "978": ["n09428293", "seashore"],
-  "979": ["n09468604", "valley"],
-  "980": ["n09472597", "volcano"],
-  "981": ["n09835506", "ballplayer"],
-  "982": ["n10148035", "groom"],
-  "983": ["n10565667", "scuba_diver"],
-  "984": ["n11879895", "rapeseed"],
-  "985": ["n11939491", "daisy"],
-  "986": ["n12057211", "yellow_lady's_slipper"],
-  "987": ["n12144580", "corn"],
-  "988": ["n12267677", "acorn"],
-  "989": ["n12620546", "hip"],
-  "990": ["n12768682", "buckeye"],
-  "991": ["n12985857", "coral_fungus"],
-  "992": ["n12998815", "agaric"],
-  "993": ["n13037406", "gyromitra"],
-  "994": ["n13040303", "stinkhorn"],
-  "995": ["n13044778", "earthstar"],
-  "996": ["n13052670", "hen-of-the-woods"],
-  "997": ["n13054560", "bolete"],
-  "998": ["n13133613", "ear"],
-  "999": ["n15075141", "toilet_tissue"]
-}

+ 0 - 46
machine_learning/app/main.py

@@ -1,46 +0,0 @@
-from pydantic import BaseModel
-from fastapi import FastAPI
-
-from .object_detection import object_detection
-from .image_classifier import image_classifier
-
-from tf2_yolov4.anchors import YOLOV4_ANCHORS
-from tf2_yolov4.model import YOLOv4
-
-
-HEIGHT, WIDTH = (640, 960)
-
-# Warm up model
-image_classifier.warm_up()
-app = FastAPI()
-
-
-class TagImagePayload(BaseModel):
-    thumbnail_path: str
-
-
-@app.post("/tagImage")
-async def post_root(payload: TagImagePayload):
-    image_path = payload.thumbnail_path
-
-    if image_path[0] == '.':
-        image_path = image_path[2:]
-
-    return image_classifier.classify_image(image_path=image_path)
-
-
-@app.get("/")
-async def test():
-
-    object_detection.run_detection()
-    # image = tf.io.read_file("./app/cars.jpg")
-    # image = tf.image.decode_image(image)
-    # image = tf.image.resize(image, (HEIGHT, WIDTH))
-    # images = tf.expand_dims(image, axis=0) / 255.0
-
-    # model = YOLOv4(
-    #     (HEIGHT, WIDTH, 3),
-    #     80,
-    #     YOLOV4_ANCHORS,
-    #     "darknet",
-    # )

+ 0 - 0
machine_learning/app/object_detection/__init__.py


+ 0 - 4
machine_learning/app/object_detection/object_detection.py

@@ -1,4 +0,0 @@
-
-
-def run_detection():
-    print("run detection")

二進制
machine_learning/app/test.png


+ 0 - 8
machine_learning/requirements.txt

@@ -1,8 +0,0 @@
-opencv-python==4.5.5.64
-fastapi>=0.68.0,<0.69.0
-pydantic>=1.8.0,<2.0.0
-uvicorn>=0.15.0,<0.16.0
-tensorflow==2.8.0
-numpy==1.22.2
-pillow==9.0.1
-tf2_yolov4==0.1.0

+ 7 - 0
mobile/android/app/src/main/AndroidManifest.xml

@@ -23,4 +23,11 @@
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+
+  <queries>
+    <intent>
+      <action android:name="android.intent.action.VIEW" />
+      <data android:scheme="https" />
+    </intent>
+  </queries>
 </manifest>

+ 1 - 0
mobile/android/fastlane/metadata/android/en-US/changelogs/17.txt

@@ -0,0 +1 @@
+* Added announcement pop-up when a new released is pushed out in Github.

+ 7 - 1
mobile/ios/Runner/Info.plist

@@ -58,7 +58,7 @@
       <string>UIInterfaceOrientationPortrait</string>
       <string>UIInterfaceOrientationLandscapeLeft</string>
       <string>UIInterfaceOrientationLandscapeRight</string>
-    </array>
+  </array>
     <key>UISupportedInterfaceOrientations~ipad</key>
     <array>
       <string>UIInterfaceOrientationPortrait</string>
@@ -76,5 +76,11 @@
     <false />
     <key>CADisableMinimumFrameDurationOnPhone</key>
     <true />
+
+
+    <key>LSApplicationQueriesSchemes</key>
+    <array>
+      <string>https</string>
+    </array>
   </dict>
 </plist>

+ 1 - 1
mobile/ios/fastlane/Fastfile

@@ -19,7 +19,7 @@ platform :ios do
   desc "iOS Beta"
   lane :beta do
     increment_version_number(
-      version_number: "1.10.1"
+      version_number: "1.11.0"
     )
     increment_build_number(
       build_number: latest_testflight_build_number + 1,

+ 4 - 0
mobile/lib/constants/hive_box.dart

@@ -13,3 +13,7 @@ const String savedLoginInfoKey = "immichSavedLoginInfoKey";
 // Backup Info
 const String hiveBackupInfoBox = "immichBackupAlbumInfoBox";
 const String backupInfoKey = "immichBackupAlbumInfoKey";
+
+// Github Release Info
+const String hiveGithubReleaseInfoBox = "immichGithubReleaseInfoBox";
+const String githubReleaseInfoKey = "immichGithubReleaseInfoKey";

+ 18 - 3
mobile/lib/main.dart

@@ -5,14 +5,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/immich_colors.dart';
 import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
 import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
+import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/routing/tab_navigation_observer.dart';
 import 'package:immich_mobile/shared/providers/app_state.provider.dart';
 import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
+import 'package:immich_mobile/shared/providers/release_info.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
 import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
+import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
 import 'constants/hive_box.dart';
 
 void main() async {
@@ -24,6 +27,7 @@ void main() async {
   await Hive.openBox(userInfoBox);
   await Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox);
   await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
+  await Hive.openBox(hiveGithubReleaseInfoBox);
 
   SystemChrome.setSystemUIOverlayStyle(
     const SystemUiOverlayStyle(
@@ -48,10 +52,18 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
       case AppLifecycleState.resumed:
         debugPrint("[APP STATE] resumed");
         ref.watch(appStateProvider.notifier).state = AppStateEnum.resumed;
-        ref.watch(backupProvider.notifier).resumeBackup();
+
+        var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;
+
+        if (isAuthenticated) {
+          ref.watch(backupProvider.notifier).resumeBackup();
+          ref.watch(assetProvider.notifier).getAllAsset();
+          ref.watch(serverInfoProvider.notifier).getServerVersion();
+        }
+
         ref.watch(websocketProvider.notifier).connect();
-        ref.watch(assetProvider.notifier).getAllAsset();
-        ref.watch(serverInfoProvider.notifier).getServerVersion();
+
+        ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
 
         break;
 
@@ -95,6 +107,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
 
   @override
   Widget build(BuildContext context) {
+    ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
+
     return MaterialApp(
       debugShowCheckedModeBanner: false,
       home: Stack(
@@ -121,6 +135,7 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
             routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
           ),
           const ImmichLoadingOverlay(),
+          const VersionAnnouncementOverlay(),
         ],
       ),
     );

+ 57 - 0
mobile/lib/shared/providers/release_info.provider.dart

@@ -0,0 +1,57 @@
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:hive_flutter/hive_flutter.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/constants/hive_box.dart';
+import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
+
+class ReleaseInfoNotifier extends StateNotifier<String> {
+  ReleaseInfoNotifier() : super("");
+
+  void checkGithubReleaseInfo() async {
+    var dio = Dio();
+    var box = Hive.box(hiveGithubReleaseInfoBox);
+
+    try {
+      String? localReleaseVersion = box.get(githubReleaseInfoKey);
+
+      Response res = await dio.get(
+        "https://api.github.com/repos/alextran1502/immich/releases/latest",
+        options: Options(
+          headers: {"Accept": "application/vnd.github.v3+json"},
+        ),
+      );
+
+      if (res.statusCode == 200) {
+        String latestTagVersion = res.data["tag_name"];
+        state = latestTagVersion;
+
+        debugPrint("Local release version $localReleaseVersion");
+        debugPrint("Remote release veresion $latestTagVersion");
+
+        if (localReleaseVersion == null && latestTagVersion.isNotEmpty) {
+          VersionAnnouncementOverlayController.appLoader.show();
+          return;
+        }
+
+        if (latestTagVersion.isNotEmpty && localReleaseVersion != latestTagVersion) {
+          VersionAnnouncementOverlayController.appLoader.show();
+          return;
+        }
+      }
+    } catch (e) {
+      debugPrint("Error gettting latest release version");
+
+      state = "";
+    }
+  }
+
+  void acknowledgeNewVersion() {
+    var box = Hive.box(hiveGithubReleaseInfoBox);
+
+    box.put(githubReleaseInfoKey, state);
+    VersionAnnouncementOverlayController.appLoader.hide();
+  }
+}
+
+final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>((ref) => ReleaseInfoNotifier());

+ 0 - 5
mobile/lib/shared/providers/server_info.provider.dart

@@ -19,11 +19,6 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
 
   final ServerInfoService _serverInfoService = ServerInfoService();
 
-  getMapboxInfo() async {
-    MapboxInfo mapboxInfoRes = await _serverInfoService.getMapboxInfo();
-    state = state.copyWith(mapboxInfo: mapboxInfoRes);
-  }
-
   getServerVersion() async {
     ServerVersion? serverVersion = await _serverInfoService.getServerVersion();
 

+ 10 - 8
mobile/lib/shared/services/server_info.service.dart

@@ -1,4 +1,5 @@
 import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
 import 'package:immich_mobile/shared/models/mapbox_info.model.dart';
 import 'package:immich_mobile/shared/models/server_version.model.dart';
 import 'package:immich_mobile/shared/services/network.service.dart';
@@ -13,15 +14,16 @@ class ServerInfoService {
     return ServerInfo.fromJson(response.toString());
   }
 
-  Future<MapboxInfo> getMapboxInfo() async {
-    Response response = await _networkService.getRequest(url: 'server-info/mapbox');
-
-    return MapboxInfo.fromJson(response.toString());
-  }
-
   Future<ServerVersion?> getServerVersion() async {
-    Response response = await _networkService.getRequest(url: 'server-info/version');
+    try {
+      Response response =
+          await _networkService.getRequest(url: 'server-info/version');
+
+      return ServerVersion.fromJson(response.toString());
+    } catch (e) {
+      debugPrint("Error getting server info");
+    }
 
-    return ServerVersion.fromJson(response.toString());
+    return null;
   }
 }

+ 133 - 0
mobile/lib/shared/views/version_announcement_overlay.dart

@@ -0,0 +1,133 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/shared/providers/release_info.provider.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class VersionAnnouncementOverlay extends HookConsumerWidget {
+  const VersionAnnouncementOverlay({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    void goToReleaseNote() async {
+      final Uri _url = Uri.parse('https://github.com/alextran1502/immich/releases/latest');
+      await launchUrl(_url);
+    }
+
+    void onAcknowledgeTapped() {
+      ref.watch(releaseInfoProvider.notifier).acknowledgeNewVersion();
+    }
+
+    return ValueListenableBuilder<bool>(
+      valueListenable: VersionAnnouncementOverlayController.appLoader.loaderShowingNotifier,
+      builder: (context, shouldShow, child) {
+        if (shouldShow) {
+          return Scaffold(
+            backgroundColor: Colors.black38,
+            body: Center(
+              child: ConstrainedBox(
+                constraints: const BoxConstraints(maxWidth: 307),
+                child: Wrap(
+                  children: [
+                    Card(
+                      child: Padding(
+                        padding: const EdgeInsets.all(30.0),
+                        child: Column(
+                          crossAxisAlignment: CrossAxisAlignment.start,
+                          children: [
+                            const Text(
+                              "New Server Version Available 🎉",
+                              style: TextStyle(
+                                fontSize: 16,
+                                fontFamily: 'WorkSans',
+                                fontWeight: FontWeight.bold,
+                                color: Colors.indigo,
+                              ),
+                            ),
+                            Padding(
+                              padding: const EdgeInsets.only(top: 16.0),
+                              child: RichText(
+                                text: TextSpan(
+                                  style: const TextStyle(
+                                      fontSize: 14, fontFamily: 'WorkSans', color: Colors.black87, height: 1.2),
+                                  children: <TextSpan>[
+                                    const TextSpan(
+                                      text: 'Hi friend, there is a new release of',
+                                    ),
+                                    const TextSpan(
+                                      text: ' Immich ',
+                                      style: TextStyle(
+                                        fontFamily: "SnowBurstOne",
+                                        color: Colors.indigo,
+                                        fontWeight: FontWeight.bold,
+                                      ),
+                                    ),
+                                    const TextSpan(
+                                      text: "please take your time to visit the ",
+                                    ),
+                                    TextSpan(
+                                      text: "release note",
+                                      style: const TextStyle(
+                                        decoration: TextDecoration.underline,
+                                      ),
+                                      recognizer: TapGestureRecognizer()..onTap = goToReleaseNote,
+                                    ),
+                                    const TextSpan(
+                                      text:
+                                          " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
+                                    )
+                                  ],
+                                ),
+                              ),
+                            ),
+                            Padding(
+                              padding: const EdgeInsets.only(top: 16.0),
+                              child: ElevatedButton(
+                                  style: ElevatedButton.styleFrom(
+                                    shape: const StadiumBorder(),
+                                    visualDensity: VisualDensity.standard,
+                                    primary: Colors.indigo,
+                                    onPrimary: Colors.grey[50],
+                                    elevation: 2,
+                                    padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
+                                  ),
+                                  onPressed: onAcknowledgeTapped,
+                                  child: const Text(
+                                    "Acknowledge",
+                                    style: TextStyle(
+                                      fontSize: 14,
+                                    ),
+                                  )),
+                            )
+                          ],
+                        ),
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+            ),
+          );
+        } else {
+          return Container();
+        }
+      },
+    );
+  }
+}
+
+class VersionAnnouncementOverlayController {
+  static final VersionAnnouncementOverlayController appLoader = VersionAnnouncementOverlayController();
+  ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
+  ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
+
+  void show() {
+    loaderShowingNotifier.value = true;
+  }
+
+  void hide() {
+    loaderShowingNotifier.value = false;
+  }
+}

+ 56 - 0
mobile/pubspec.lock

@@ -1015,6 +1015,62 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.0.4"
+  url_launcher:
+    dependency: "direct main"
+    description:
+      name: url_launcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.3"
+  url_launcher_android:
+    dependency: transitive
+    description:
+      name: url_launcher_android
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.0.17"
+  url_launcher_ios:
+    dependency: transitive
+    description:
+      name: url_launcher_ios
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.0.17"
+  url_launcher_linux:
+    dependency: transitive
+    description:
+      name: url_launcher_linux
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
+  url_launcher_macos:
+    dependency: transitive
+    description:
+      name: url_launcher_macos
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
+  url_launcher_platform_interface:
+    dependency: transitive
+    description:
+      name: url_launcher_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.5"
+  url_launcher_web:
+    dependency: transitive
+    description:
+      name: url_launcher_web
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.11"
+  url_launcher_windows:
+    dependency: transitive
+    description:
+      name: url_launcher_windows
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
   uuid:
     dependency: transitive
     description:

+ 2 - 1
mobile/pubspec.yaml

@@ -2,7 +2,7 @@ name: immich_mobile
 description: Immich - selfhosted backup media file on mobile phone
 
 publish_to: "none"
-version: 1.10.1+16
+version: 1.11.0+17
 
 environment:
   sdk: ">=2.15.1 <3.0.0"
@@ -39,6 +39,7 @@ dependencies:
   flutter_swipe_detector: ^2.0.0
   equatable: ^2.0.3
   image_picker: ^0.8.5+3
+  url_launcher: ^6.1.3
 
 dev_dependencies:
   flutter_test:

+ 1 - 1
server/Dockerfile

@@ -1,4 +1,4 @@
-FROM node:16-alpine3.14
+FROM node:16-alpine3.14 as core
 
 ARG DEBIAN_FRONTEND=noninteractive
 

+ 23 - 7
server/src/api-v1/asset/asset.controller.ts → server/apps/immich/src/api-v1/asset/asset.controller.ts

@@ -23,7 +23,7 @@ import { assetUploadOption } from '../../config/asset-upload.config';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { CreateAssetDto } from './dto/create-asset.dto';
 import { ServeFileDto } from './dto/serve-file.dto';
-import { AssetEntity } from './entities/asset.entity';
+import { AssetEntity } from '@app/database/entities/asset.entity';
 import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
 import { Response as Res } from 'express';
 import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto';
@@ -31,6 +31,8 @@ import { BackgroundTaskService } from '../../modules/background-task/background-
 import { DeleteAssetDto } from './dto/delete-asset.dto';
 import { SearchAssetDto } from './dto/search-asset.dto';
 import { CommunicationGateway } from '../communication/communication.gateway';
+import { InjectQueue } from '@nestjs/bull';
+import { Queue } from 'bull';
 
 @UseGuards(JwtAuthGuard)
 @Controller('asset')
@@ -39,7 +41,10 @@ export class AssetController {
     private wsCommunicateionGateway: CommunicationGateway,
     private assetService: AssetService,
     private backgroundTaskService: BackgroundTaskService,
-  ) { }
+
+    @InjectQueue('asset-uploaded-queue')
+    private assetUploadedQueue: Queue,
+  ) {}
 
   @Post('upload')
   @UseInterceptors(
@@ -61,13 +66,24 @@ export class AssetController {
         const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
 
         if (uploadFiles.thumbnailData != null && savedAsset) {
-          await this.assetService.updateThumbnailInfo(savedAsset.id, uploadFiles.thumbnailData[0].path);
-          await this.backgroundTaskService.tagImage(uploadFiles.thumbnailData[0].path, savedAsset);
-          await this.backgroundTaskService.detectObject(uploadFiles.thumbnailData[0].path, savedAsset);
+          const assetWithThumbnail = await this.assetService.updateThumbnailInfo(
+            savedAsset,
+            uploadFiles.thumbnailData[0].path,
+          );
+
+          await this.assetUploadedQueue.add(
+            'asset-uploaded',
+            { asset: assetWithThumbnail, fileName: file.originalname, fileSize: file.size, hasThumbnail: true },
+            { jobId: savedAsset.id },
+          );
+        } else {
+          await this.assetUploadedQueue.add(
+            'asset-uploaded',
+            { asset: savedAsset, fileName: file.originalname, fileSize: file.size, hasThumbnail: false },
+            { jobId: savedAsset.id },
+          );
         }
 
-        await this.backgroundTaskService.extractExif(savedAsset, file.originalname, file.size);
-
         this.wsCommunicateionGateway.server.to(savedAsset.userId).emit('on_upload_success', JSON.stringify(savedAsset));
       } catch (e) {
         Logger.error(`Error receiving upload file ${e}`);

+ 6 - 18
server/src/api-v1/asset/asset.module.ts → server/apps/immich/src/api-v1/asset/asset.module.ts

@@ -2,9 +2,7 @@ import { Module } from '@nestjs/common';
 import { AssetService } from './asset.service';
 import { AssetController } from './asset.controller';
 import { TypeOrmModule } from '@nestjs/typeorm';
-import { AssetEntity } from './entities/asset.entity';
-import { ImageOptimizeModule } from '../../modules/image-optimize/image-optimize.module';
-import { AssetOptimizeService } from '../../modules/image-optimize/image-optimize.service';
+import { AssetEntity } from '@app/database/entities/asset.entity';
 import { BullModule } from '@nestjs/bull';
 import { BackgroundTaskModule } from '../../modules/background-task/background-task.module';
 import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
@@ -13,29 +11,19 @@ import { CommunicationModule } from '../communication/communication.module';
 @Module({
   imports: [
     CommunicationModule,
-
-    BullModule.registerQueue({
-      name: 'optimize',
-      defaultJobOptions: {
-        attempts: 3,
-        removeOnComplete: true,
-        removeOnFail: false,
-      },
-    }),
+    BackgroundTaskModule,
+    TypeOrmModule.forFeature([AssetEntity]),
     BullModule.registerQueue({
-      name: 'background-task',
+      name: 'asset-uploaded-queue',
       defaultJobOptions: {
         attempts: 3,
         removeOnComplete: true,
         removeOnFail: false,
       },
     }),
-    TypeOrmModule.forFeature([AssetEntity]),
-    ImageOptimizeModule,
-    BackgroundTaskModule,
   ],
   controllers: [AssetController],
-  providers: [AssetService, AssetOptimizeService, BackgroundTaskService],
+  providers: [AssetService, BackgroundTaskService],
   exports: [],
 })
-export class AssetModule { }
+export class AssetModule {}

+ 134 - 115
server/src/api-v1/asset/asset.service.ts → server/apps/immich/src/api-v1/asset/asset.service.ts

@@ -1,9 +1,9 @@
-import { BadRequestException, Injectable, Logger, StreamableFile } from '@nestjs/common';
+import { BadRequestException, Injectable, InternalServerErrorException, Logger, StreamableFile } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { Repository } from 'typeorm';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { CreateAssetDto } from './dto/create-asset.dto';
-import { AssetEntity, AssetType } from './entities/asset.entity';
+import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
 import _ from 'lodash';
 import { createReadStream, stat } from 'fs';
 import { ServeFileDto } from './dto/serve-file.dto';
@@ -11,7 +11,6 @@ import { Response as Res } from 'express';
 import { promisify } from 'util';
 import { DeleteAssetDto } from './dto/delete-asset.dto';
 import { SearchAssetDto } from './dto/search-asset.dto';
-import ffmpeg from 'fluent-ffmpeg';
 
 const fileInfo = promisify(stat);
 
@@ -20,12 +19,18 @@ export class AssetService {
   constructor(
     @InjectRepository(AssetEntity)
     private assetRepository: Repository<AssetEntity>,
-  ) { }
-
-  public async updateThumbnailInfo(assetId: string, path: string) {
-    return await this.assetRepository.update(assetId, {
-      resizePath: path,
-    });
+  ) {}
+
+  public async updateThumbnailInfo(asset: AssetEntity, thumbnailPath: string): Promise<AssetEntity> {
+    const updatedAsset = await this.assetRepository
+      .createQueryBuilder('assets')
+      .update<AssetEntity>(AssetEntity, { ...asset, resizePath: thumbnailPath })
+      .where('assets.id = :id', { id: asset.id })
+      .returning('*')
+      .updateEntity(true)
+      .execute();
+
+    return updatedAsset.raw[0];
   }
 
   public async createUserAsset(authUser: AuthUserDto, assetInfo: CreateAssetDto, path: string, mimeType: string) {
@@ -66,13 +71,13 @@ export class AssetService {
     try {
       return await this.assetRepository.find({
         where: {
-          userId: authUser.id
+          userId: authUser.id,
         },
         relations: ['exifInfo'],
         order: {
-          createdAt: 'DESC'
-        }
-      })
+          createdAt: 'DESC',
+        },
+      });
     } catch (e) {
       Logger.error(e, 'getAllAssets');
     }
@@ -101,35 +106,45 @@ export class AssetService {
   }
 
   public async downloadFile(query: ServeFileDto, res: Res) {
-    let file = null;
-    const asset = await this.findOne(query.did, query.aid);
+    try {
+      let file = null;
+      const asset = await this.findOne(query.did, query.aid);
 
-    if (query.isThumb === 'false' || !query.isThumb) {
-      const { size } = await fileInfo(asset.originalPath);
-      res.set({
-        'Content-Type': asset.mimeType,
-        'Content-Length': size,
-      });
-      file = createReadStream(asset.originalPath);
-    } else {
-      const { size } = await fileInfo(asset.resizePath);
-      res.set({
-        'Content-Type': 'image/jpeg',
-        'Content-Length': size,
-      });
-      file = createReadStream(asset.resizePath);
-    }
+      if (query.isThumb === 'false' || !query.isThumb) {
+        const { size } = await fileInfo(asset.originalPath);
+        res.set({
+          'Content-Type': asset.mimeType,
+          'Content-Length': size,
+        });
+        file = createReadStream(asset.originalPath);
+      } else {
+        const { size } = await fileInfo(asset.resizePath);
+        res.set({
+          'Content-Type': 'image/jpeg',
+          'Content-Length': size,
+        });
+        file = createReadStream(asset.resizePath);
+      }
 
-    return new StreamableFile(file);
+      return new StreamableFile(file);
+    } catch (e) {
+      Logger.error('Error download asset ', e);
+      throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile');
+    }
   }
 
   public async getAssetThumbnail(assetId: string) {
-    const asset = await this.assetRepository.findOne({ id: assetId });
+    try {
+      const asset = await this.assetRepository.findOne({ id: assetId });
 
-    if (asset.webpPath != '') {
-      return new StreamableFile(createReadStream(asset.webpPath));
-    } else {
-      return new StreamableFile(createReadStream(asset.resizePath));
+      if (asset.webpPath && asset.webpPath.length > 0) {
+        return new StreamableFile(createReadStream(asset.webpPath));
+      } else {
+        return new StreamableFile(createReadStream(asset.resizePath));
+      }
+    } catch (e) {
+      Logger.error('Error serving asset thumbnail ', e);
+      throw new InternalServerErrorException('Failed to serve asset thumbnail', 'GetAssetThumbnail');
     }
   }
 
@@ -141,7 +156,6 @@ export class AssetService {
       throw new BadRequestException('Asset does not exist');
     }
 
-
     // Handle Sending Images
     if (asset.type == AssetType.IMAGE || query.isThumb == 'true') {
       /**
@@ -154,97 +168,102 @@ export class AssetService {
         return new StreamableFile(createReadStream(asset.resizePath));
       }
 
-
-      /**
-       * Serve thumbnail image for both web and mobile app
-       */
-      if (query.isThumb === 'false' || !query.isThumb) {
-        res.set({
-          'Content-Type': asset.mimeType,
-        });
-        file = createReadStream(asset.originalPath);
-      } else {
-        if (asset.webpPath != '') {
+      try {
+        /**
+         * Serve thumbnail image for both web and mobile app
+         */
+        if (query.isThumb === 'false' || !query.isThumb) {
           res.set({
-            'Content-Type': 'image/webp',
+            'Content-Type': asset.mimeType,
           });
-          file = createReadStream(asset.webpPath);
+          file = createReadStream(asset.originalPath);
         } else {
-          res.set({
-            'Content-Type': 'image/jpeg',
-          });
-          file = createReadStream(asset.resizePath);
+          if (asset.webpPath && asset.webpPath.length > 0) {
+            res.set({
+              'Content-Type': 'image/webp',
+            });
+
+            file = createReadStream(asset.webpPath);
+          } else {
+            res.set({
+              'Content-Type': 'image/jpeg',
+            });
+            file = createReadStream(asset.resizePath);
+          }
         }
-      }
-
-      file.on('error', (error) => {
-        Logger.log(`Cannot create read stream ${error}`);
-        return new BadRequestException('Cannot Create Read Stream');
-      });
 
-      return new StreamableFile(file);
-
-    } else if (asset.type == AssetType.VIDEO) {
-      // Handle Video
-      let videoPath = asset.originalPath;
-      let mimeType = asset.mimeType;
+        file.on('error', (error) => {
+          Logger.log(`Cannot create read stream ${error}`);
+          return new BadRequestException('Cannot Create Read Stream');
+        });
 
-      if (query.isWeb && asset.mimeType == 'video/quicktime') {
-        videoPath = asset.encodedVideoPath == '' ? asset.originalPath : asset.encodedVideoPath;
-        mimeType = asset.encodedVideoPath == '' ? asset.mimeType : 'video/mp4';
+        return new StreamableFile(file);
+      } catch (e) {
+        Logger.error('Error serving IMAGE asset ', e);
+        throw new InternalServerErrorException(`Failed to serve image asset ${e}`, 'ServeFile');
       }
-
-      const { size } = await fileInfo(videoPath);
-      const range = headers.range;
-
-      if (range) {
-        /** Extracting Start and End value from Range Header */
-        let [start, end] = range.replace(/bytes=/, '').split('-');
-        start = parseInt(start, 10);
-        end = end ? parseInt(end, 10) : size - 1;
-
-        if (!isNaN(start) && isNaN(end)) {
-          start = start;
-          end = size - 1;
-        }
-        if (isNaN(start) && !isNaN(end)) {
-          start = size - end;
-          end = size - 1;
+    } else if (asset.type == AssetType.VIDEO) {
+      try {
+        // Handle Video
+        let videoPath = asset.originalPath;
+        let mimeType = asset.mimeType;
+
+        if (query.isWeb && asset.mimeType == 'video/quicktime') {
+          videoPath = asset.encodedVideoPath == '' ? asset.originalPath : asset.encodedVideoPath;
+          mimeType = asset.encodedVideoPath == '' ? asset.mimeType : 'video/mp4';
         }
 
-        // Handle unavailable range request
-        if (start >= size || end >= size) {
-          console.error('Bad Request');
-          // Return the 416 Range Not Satisfiable.
-          res.status(416).set({
-            'Content-Range': `bytes */${size}`,
+        const { size } = await fileInfo(videoPath);
+        const range = headers.range;
+
+        if (range) {
+          /** Extracting Start and End value from Range Header */
+          let [start, end] = range.replace(/bytes=/, '').split('-');
+          start = parseInt(start, 10);
+          end = end ? parseInt(end, 10) : size - 1;
+
+          if (!isNaN(start) && isNaN(end)) {
+            start = start;
+            end = size - 1;
+          }
+          if (isNaN(start) && !isNaN(end)) {
+            start = size - end;
+            end = size - 1;
+          }
+
+          // Handle unavailable range request
+          if (start >= size || end >= size) {
+            console.error('Bad Request');
+            // Return the 416 Range Not Satisfiable.
+            res.status(416).set({
+              'Content-Range': `bytes */${size}`,
+            });
+
+            throw new BadRequestException('Bad Request Range');
+          }
+
+          /** Sending Partial Content With HTTP Code 206 */
+
+          res.status(206).set({
+            'Content-Range': `bytes ${start}-${end}/${size}`,
+            'Accept-Ranges': 'bytes',
+            'Content-Length': end - start + 1,
+            'Content-Type': mimeType,
           });
 
-          throw new BadRequestException('Bad Request Range');
-        }
-
-        /** Sending Partial Content With HTTP Code 206 */
-
-        res.status(206).set({
-          'Content-Range': `bytes ${start}-${end}/${size}`,
-          'Accept-Ranges': 'bytes',
-          'Content-Length': end - start + 1,
-          'Content-Type': mimeType,
-        });
-
-
-        const videoStream = createReadStream(videoPath, { start: start, end: end });
-
-        return new StreamableFile(videoStream);
-
+          const videoStream = createReadStream(videoPath, { start: start, end: end });
 
-      } else {
-
-        res.set({
-          'Content-Type': mimeType,
-        });
+          return new StreamableFile(videoStream);
+        } else {
+          res.set({
+            'Content-Type': mimeType,
+          });
 
-        return new StreamableFile(createReadStream(videoPath));
+          return new StreamableFile(createReadStream(videoPath));
+        }
+      } catch (e) {
+        Logger.error('Error serving VIDEO asset ', e);
+        throw new InternalServerErrorException(`Failed to serve video asset ${e}`, 'ServeFile');
       }
     }
   }

+ 1 - 1
server/src/api-v1/asset/dto/create-asset.dto.ts → server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts

@@ -1,5 +1,5 @@
 import { IsNotEmpty, IsOptional } from 'class-validator';
-import { AssetType } from '../entities/asset.entity';
+import { AssetType } from '@app/database/entities/asset.entity';
 
 export class CreateAssetDto {
   @IsNotEmpty()

+ 0 - 0
server/src/api-v1/asset/dto/create-exif.dto.ts → server/apps/immich/src/api-v1/asset/dto/create-exif.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/delete-asset.dto.ts → server/apps/immich/src/api-v1/asset/dto/delete-asset.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/get-all-asset-query.dto.ts → server/apps/immich/src/api-v1/asset/dto/get-all-asset-query.dto.ts


+ 1 - 1
server/src/api-v1/asset/dto/get-all-asset-response.dto.ts → server/apps/immich/src/api-v1/asset/dto/get-all-asset-response.dto.ts

@@ -1,4 +1,4 @@
-import { AssetEntity } from '../entities/asset.entity';
+import { AssetEntity } from '@app/database/entities/asset.entity';
 
 export class GetAllAssetReponseDto {
   data: Array<{ date: string; assets: Array<AssetEntity> }>;

+ 0 - 0
server/src/api-v1/asset/dto/get-asset.dto.ts → server/apps/immich/src/api-v1/asset/dto/get-asset.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/get-new-asset-query.dto.ts → server/apps/immich/src/api-v1/asset/dto/get-new-asset-query.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/search-asset.dto.ts → server/apps/immich/src/api-v1/asset/dto/search-asset.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/serve-file.dto.ts → server/apps/immich/src/api-v1/asset/dto/serve-file.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/update-asset.dto.ts → server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts


+ 0 - 0
server/src/api-v1/asset/dto/update-exif.dto.ts → server/apps/immich/src/api-v1/asset/dto/update-exif.dto.ts


+ 1 - 1
server/src/api-v1/auth/auth.controller.ts → server/apps/immich/src/api-v1/auth/auth.controller.ts

@@ -7,7 +7,7 @@ import { SignUpDto } from './dto/sign-up.dto';
 
 @Controller('auth')
 export class AuthController {
-  constructor(private readonly authService: AuthService) { }
+  constructor(private readonly authService: AuthService) {}
 
   @Post('/login')
   async login(@Body(ValidationPipe) loginCredential: LoginCredentialDto) {

+ 1 - 1
server/src/api-v1/auth/auth.module.ts → server/apps/immich/src/api-v1/auth/auth.module.ts

@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
 import { AuthService } from './auth.service';
 import { AuthController } from './auth.controller';
 import { TypeOrmModule } from '@nestjs/typeorm';
-import { UserEntity } from '../user/entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
 import { JwtModule } from '@nestjs/jwt';

+ 1 - 1
server/src/api-v1/auth/auth.service.ts → server/apps/immich/src/api-v1/auth/auth.service.ts

@@ -1,7 +1,7 @@
 import { BadRequestException, Injectable, InternalServerErrorException, Logger } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { Repository } from 'typeorm';
-import { UserEntity } from '../user/entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 import { LoginCredentialDto } from './dto/login-credential.dto';
 import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { JwtPayloadDto } from './dto/jwt-payload.dto';

+ 0 - 0
server/src/api-v1/auth/dto/jwt-payload.dto.ts → server/apps/immich/src/api-v1/auth/dto/jwt-payload.dto.ts


+ 0 - 0
server/src/api-v1/auth/dto/login-credential.dto.ts → server/apps/immich/src/api-v1/auth/dto/login-credential.dto.ts


+ 0 - 0
server/src/api-v1/auth/dto/sign-up.dto.ts → server/apps/immich/src/api-v1/auth/dto/sign-up.dto.ts


+ 1 - 1
server/src/api-v1/communication/communication.gateway.ts → server/apps/immich/src/api-v1/communication/communication.gateway.ts

@@ -4,7 +4,7 @@ import { Socket, Server } from 'socket.io';
 import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { Logger } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
-import { UserEntity } from '../user/entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 import { Repository } from 'typeorm';
 
 @WebSocketGateway()

+ 1 - 1
server/src/api-v1/communication/communication.module.ts → server/apps/immich/src/api-v1/communication/communication.module.ts

@@ -6,7 +6,7 @@ import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { JwtModule } from '@nestjs/jwt';
 import { jwtConfig } from '../../config/jwt.config';
 import { TypeOrmModule } from '@nestjs/typeorm';
-import { UserEntity } from '../user/entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 
 @Module({
   imports: [TypeOrmModule.forFeature([UserEntity]), ImmichJwtModule, JwtModule.register(jwtConfig)],

+ 0 - 0
server/src/api-v1/communication/communication.service.ts → server/apps/immich/src/api-v1/communication/communication.service.ts


+ 0 - 0
server/src/api-v1/device-info/device-info.controller.ts → server/apps/immich/src/api-v1/device-info/device-info.controller.ts


+ 1 - 1
server/src/api-v1/device-info/device-info.module.ts → server/apps/immich/src/api-v1/device-info/device-info.module.ts

@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
 import { DeviceInfoService } from './device-info.service';
 import { DeviceInfoController } from './device-info.controller';
 import { TypeOrmModule } from '@nestjs/typeorm';
-import { DeviceInfoEntity } from './entities/device-info.entity';
+import { DeviceInfoEntity } from '@app/database/entities/device-info.entity';
 
 @Module({
   imports: [TypeOrmModule.forFeature([DeviceInfoEntity])],

+ 1 - 1
server/src/api-v1/device-info/device-info.service.ts → server/apps/immich/src/api-v1/device-info/device-info.service.ts

@@ -4,7 +4,7 @@ import { Repository } from 'typeorm';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { CreateDeviceInfoDto } from './dto/create-device-info.dto';
 import { UpdateDeviceInfoDto } from './dto/update-device-info.dto';
-import { DeviceInfoEntity } from './entities/device-info.entity';
+import { DeviceInfoEntity } from '@app/database/entities/device-info.entity';
 
 @Injectable()
 export class DeviceInfoService {

+ 1 - 1
server/src/api-v1/device-info/dto/create-device-info.dto.ts → server/apps/immich/src/api-v1/device-info/dto/create-device-info.dto.ts

@@ -1,5 +1,5 @@
 import { IsNotEmpty, IsOptional } from 'class-validator';
-import { DeviceType } from '../entities/device-info.entity';
+import { DeviceType } from '@app/database/entities/device-info.entity';
 
 export class CreateDeviceInfoDto {
   @IsNotEmpty()

+ 1 - 1
server/src/api-v1/device-info/dto/update-device-info.dto.ts → server/apps/immich/src/api-v1/device-info/dto/update-device-info.dto.ts

@@ -1,6 +1,6 @@
 import { PartialType } from '@nestjs/mapped-types';
 import { IsOptional } from 'class-validator';
-import { DeviceType } from '../entities/device-info.entity';
+import { DeviceType } from '@app/database/entities/device-info.entity';
 import { CreateDeviceInfoDto } from './create-device-info.dto';
 
 export class UpdateDeviceInfoDto extends PartialType(CreateDeviceInfoDto) {}

+ 0 - 0
server/src/api-v1/server-info/dto/server-info.dto.ts → server/apps/immich/src/api-v1/server-info/dto/server-info.dto.ts


+ 0 - 0
server/src/api-v1/server-info/server-info.controller.ts → server/apps/immich/src/api-v1/server-info/server-info.controller.ts


+ 1 - 1
server/src/api-v1/server-info/server-info.module.ts → server/apps/immich/src/api-v1/server-info/server-info.module.ts

@@ -4,6 +4,6 @@ import { ServerInfoController } from './server-info.controller';
 
 @Module({
   controllers: [ServerInfoController],
-  providers: [ServerInfoService]
+  providers: [ServerInfoService],
 })
 export class ServerInfoModule {}

+ 0 - 0
server/src/api-v1/server-info/server-info.service.ts → server/apps/immich/src/api-v1/server-info/server-info.service.ts


+ 1 - 1
server/src/api-v1/sharing/dto/add-assets.dto.ts → server/apps/immich/src/api-v1/sharing/dto/add-assets.dto.ts

@@ -1,5 +1,5 @@
 import { IsNotEmpty } from 'class-validator';
-import { AssetEntity } from '../../asset/entities/asset.entity';
+import { AssetEntity } from '@app/database/entities/asset.entity';
 
 export class AddAssetsDto {
   @IsNotEmpty()

+ 0 - 0
server/src/api-v1/sharing/dto/add-users.dto.ts → server/apps/immich/src/api-v1/sharing/dto/add-users.dto.ts


+ 1 - 1
server/src/api-v1/sharing/dto/create-shared-album.dto.ts → server/apps/immich/src/api-v1/sharing/dto/create-shared-album.dto.ts

@@ -1,5 +1,5 @@
 import { IsNotEmpty, IsOptional } from 'class-validator';
-import { AssetEntity } from '../../asset/entities/asset.entity';
+import { AssetEntity } from '@app/database/entities/asset.entity';
 
 export class CreateSharedAlbumDto {
   @IsNotEmpty()

+ 0 - 0
server/src/api-v1/sharing/dto/remove-assets.dto.ts → server/apps/immich/src/api-v1/sharing/dto/remove-assets.dto.ts


+ 0 - 0
server/src/api-v1/sharing/dto/update-shared-album.dto.ts → server/apps/immich/src/api-v1/sharing/dto/update-shared-album.dto.ts


+ 0 - 0
server/src/api-v1/sharing/sharing.controller.ts → server/apps/immich/src/api-v1/sharing/sharing.controller.ts


+ 5 - 5
server/src/api-v1/sharing/sharing.module.ts → server/apps/immich/src/api-v1/sharing/sharing.module.ts

@@ -2,11 +2,11 @@ import { Module } from '@nestjs/common';
 import { SharingService } from './sharing.service';
 import { SharingController } from './sharing.controller';
 import { TypeOrmModule } from '@nestjs/typeorm';
-import { AssetEntity } from '../asset/entities/asset.entity';
-import { UserEntity } from '../user/entities/user.entity';
-import { SharedAlbumEntity } from './entities/shared-album.entity';
-import { AssetSharedAlbumEntity } from './entities/asset-shared-album.entity';
-import { UserSharedAlbumEntity } from './entities/user-shared-album.entity';
+import { AssetEntity } from '@app/database/entities/asset.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
+import { AssetSharedAlbumEntity } from '@app/database/entities/asset-shared-album.entity';
+import { SharedAlbumEntity } from '@app/database/entities/shared-album.entity';
+import { UserSharedAlbumEntity } from '@app/database/entities/user-shared-album.entity';
 
 @Module({
   imports: [

+ 5 - 5
server/src/api-v1/sharing/sharing.service.ts → server/apps/immich/src/api-v1/sharing/sharing.service.ts

@@ -2,13 +2,13 @@ import { BadRequestException, Injectable, NotFoundException, UnauthorizedExcepti
 import { InjectRepository } from '@nestjs/typeorm';
 import { getConnection, Repository } from 'typeorm';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
-import { AssetEntity } from '../asset/entities/asset.entity';
-import { UserEntity } from '../user/entities/user.entity';
+import { AssetEntity } from '@app/database/entities/asset.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 import { AddAssetsDto } from './dto/add-assets.dto';
 import { CreateSharedAlbumDto } from './dto/create-shared-album.dto';
-import { AssetSharedAlbumEntity } from './entities/asset-shared-album.entity';
-import { SharedAlbumEntity } from './entities/shared-album.entity';
-import { UserSharedAlbumEntity } from './entities/user-shared-album.entity';
+import { AssetSharedAlbumEntity } from '@app/database/entities/asset-shared-album.entity';
+import { SharedAlbumEntity } from '@app/database/entities/shared-album.entity';
+import { UserSharedAlbumEntity } from '@app/database/entities/user-shared-album.entity';
 import _ from 'lodash';
 import { AddUsersDto } from './dto/add-users.dto';
 import { RemoveAssetsDto } from './dto/remove-assets.dto';

+ 0 - 0
server/src/api-v1/user/dto/create-user.dto.ts → server/apps/immich/src/api-v1/user/dto/create-user.dto.ts


+ 0 - 0
server/src/api-v1/user/dto/update-user.dto.ts → server/apps/immich/src/api-v1/user/dto/update-user.dto.ts


+ 1 - 1
server/src/api-v1/user/response-dto/user.ts → server/apps/immich/src/api-v1/user/response-dto/user.ts

@@ -1,4 +1,4 @@
-import { UserEntity } from '../entities/user.entity';
+import { UserEntity } from '../../../../../../libs/database/src/entities/user.entity';
 
 export interface User {
   id: string;

+ 19 - 7
server/src/api-v1/user/user.controller.ts → server/apps/immich/src/api-v1/user/user.controller.ts

@@ -1,4 +1,19 @@
-import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, ValidationPipe, Put, Query, UseInterceptors, UploadedFile, Response } from '@nestjs/common';
+import {
+  Controller,
+  Get,
+  Post,
+  Body,
+  Patch,
+  Param,
+  Delete,
+  UseGuards,
+  ValidationPipe,
+  Put,
+  Query,
+  UseInterceptors,
+  UploadedFile,
+  Response,
+} from '@nestjs/common';
 import { UserService } from './user.service';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
@@ -11,7 +26,7 @@ import { Response as Res } from 'express';
 
 @Controller('user')
 export class UserController {
-  constructor(private readonly userService: UserService) { }
+  constructor(private readonly userService: UserService) {}
 
   @UseGuards(JwtAuthGuard)
   @Get()
@@ -28,14 +43,13 @@ export class UserController {
 
   @Get('/count')
   async getUserCount(@Query('isAdmin') isAdmin: boolean) {
-
     return await this.userService.getUserCount(isAdmin);
   }
 
   @UseGuards(JwtAuthGuard)
   @Put()
   async updateUser(@Body(ValidationPipe) updateUserDto: UpdateUserDto) {
-    return await this.userService.updateUser(updateUserDto)
+    return await this.userService.updateUser(updateUserDto);
   }
 
   @UseGuards(JwtAuthGuard)
@@ -46,9 +60,7 @@ export class UserController {
   }
 
   @Get('/profile-image/:userId')
-  async getProfileImage(@Param('userId') userId: string,
-    @Response({ passthrough: true }) res: Res,
-  ) {
+  async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res) {
     return await this.userService.getUserProfileImage(userId, res);
   }
 }

+ 2 - 2
server/src/api-v1/user/user.module.ts → server/apps/immich/src/api-v1/user/user.module.ts

@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
 import { UserService } from './user.service';
 import { UserController } from './user.controller';
 import { TypeOrmModule } from '@nestjs/typeorm';
-import { UserEntity } from './entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module';
 import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { JwtModule } from '@nestjs/jwt';
@@ -13,4 +13,4 @@ import { jwtConfig } from '../../config/jwt.config';
   controllers: [UserController],
   providers: [UserService, ImmichJwtService],
 })
-export class UserModule { }
+export class UserModule {}

+ 1 - 1
server/src/api-v1/user/user.service.ts → server/apps/immich/src/api-v1/user/user.service.ts

@@ -4,7 +4,7 @@ import { Not, Repository } from 'typeorm';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { CreateUserDto } from './dto/create-user.dto';
 import { UpdateUserDto } from './dto/update-user.dto';
-import { UserEntity } from './entities/user.entity';
+import { UserEntity } from '@app/database/entities/user.entity';
 import * as bcrypt from 'bcrypt';
 import { createReadStream } from 'fs';
 import { Response as Res } from 'express';

+ 2 - 4
server/src/app.controller.ts → server/apps/immich/src/app.controller.ts

@@ -1,15 +1,13 @@
 import { Controller, Get, Res, Headers } from '@nestjs/common';
 import { Response } from 'express';
-
 @Controller()
-
 export class AppController {
-  constructor() { }
+  constructor() {}
 
   @Get()
   async redirectToWebpage(@Res({ passthrough: true }) res: Response, @Headers() headers) {
     const host = headers.host;
 
-    return res.redirect(`http://${host}:2285`)
+    return res.redirect(`http://${host}:2285`);
   }
 }

Some files were not shown because too many files changed in this diff