mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	Added machine learning microservice and object detection (#76)
This commit is contained in:
		
							parent
							
								
									fe693db84f
								
							
						
					
					
						commit
						dd9c5244fd
					
				
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@ -1,8 +1,8 @@
 | 
				
			|||||||
dev:
 | 
					dev:
 | 
				
			||||||
	docker-compose -f ./docker/docker-compose.yml up
 | 
						docker-compose -f ./docker/docker-compose.yml up --remove-orphans
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev-update:
 | 
					dev-update:
 | 
				
			||||||
	docker-compose -f ./docker/docker-compose.yml up --build -V 
 | 
						docker-compose -f ./docker/docker-compose.yml up --build -V  --remove-orphans
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev-scale:
 | 
					dev-scale:
 | 
				
			||||||
	docker-compose -f ./docker/docker-compose.yml up --build -V  --scale immich_server=3 --remove-orphans 
 | 
						docker-compose -f ./docker/docker-compose.yml up --build -V  --scale immich_server=3 --remove-orphans 
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,34 @@ services:
 | 
				
			|||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      - immich_network
 | 
					      - immich_network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  immich_microservices:
 | 
				
			||||||
 | 
					    image: immich-microservices-dev:1.3.2
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: ../microservices
 | 
				
			||||||
 | 
					      target: development
 | 
				
			||||||
 | 
					      dockerfile: ../microservices/Dockerfile
 | 
				
			||||||
 | 
					    command: npm run start:dev
 | 
				
			||||||
 | 
					    deploy:
 | 
				
			||||||
 | 
					      resources:
 | 
				
			||||||
 | 
					        reservations:
 | 
				
			||||||
 | 
					          devices:
 | 
				
			||||||
 | 
					            - driver: nvidia
 | 
				
			||||||
 | 
					              count: 1
 | 
				
			||||||
 | 
					              capabilities: [ gpu ]
 | 
				
			||||||
 | 
					    expose:
 | 
				
			||||||
 | 
					      - "3001"
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ../microservices:/usr/src/app
 | 
				
			||||||
 | 
					      - ${UPLOAD_LOCATION}:/usr/src/app/upload
 | 
				
			||||||
 | 
					      - /usr/src/app/node_modules
 | 
				
			||||||
 | 
					    env_file:
 | 
				
			||||||
 | 
					      - .env
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - database
 | 
				
			||||||
 | 
					      - immich_server
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - immich_network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  redis:
 | 
					  redis:
 | 
				
			||||||
    container_name: immich_redis
 | 
					    container_name: immich_redis
 | 
				
			||||||
    image: redis:6.2
 | 
					    image: redis:6.2
 | 
				
			||||||
@ -60,35 +88,6 @@ services:
 | 
				
			|||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - immich_server
 | 
					      - immich_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  immich_tf_fastapi:
 | 
					 | 
				
			||||||
    container_name: immich_tf_fastapi
 | 
					 | 
				
			||||||
    image: tensor_flow_fastapi:1.0.0
 | 
					 | 
				
			||||||
    restart: always
 | 
					 | 
				
			||||||
    command: uvicorn app.main:app --proxy-headers --host 0.0.0.0 --port 8000 --reload
 | 
					 | 
				
			||||||
    build:
 | 
					 | 
				
			||||||
      context: ../machine_learning
 | 
					 | 
				
			||||||
      target: gpu
 | 
					 | 
				
			||||||
      dockerfile: ../machine_learning/Dockerfile
 | 
					 | 
				
			||||||
    deploy:
 | 
					 | 
				
			||||||
      resources:
 | 
					 | 
				
			||||||
        reservations:
 | 
					 | 
				
			||||||
          devices:
 | 
					 | 
				
			||||||
            - driver: nvidia
 | 
					 | 
				
			||||||
              count: 1
 | 
					 | 
				
			||||||
              capabilities: [gpu]
 | 
					 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - ../machine_learning/app:/code/app
 | 
					 | 
				
			||||||
      - ${UPLOAD_LOCATION}:/code/app/upload
 | 
					 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
      - 2285:8000
 | 
					 | 
				
			||||||
    expose:
 | 
					 | 
				
			||||||
      - "8000"
 | 
					 | 
				
			||||||
    depends_on:
 | 
					 | 
				
			||||||
      - database
 | 
					 | 
				
			||||||
    networks:
 | 
					 | 
				
			||||||
      - immich_network
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      
 | 
					 | 
				
			||||||
networks:
 | 
					networks:
 | 
				
			||||||
  immich_network:
 | 
					  immich_network:
 | 
				
			||||||
volumes:
 | 
					volumes:
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,27 @@ services:
 | 
				
			|||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      - immich_network
 | 
					      - immich_network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  immich_microservices:
 | 
				
			||||||
 | 
					    image: immich-microservices-dev:1.3.2
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: ../microservices
 | 
				
			||||||
 | 
					      target: development
 | 
				
			||||||
 | 
					      dockerfile: ../microservices/Dockerfile
 | 
				
			||||||
 | 
					    command: npm run start:dev
 | 
				
			||||||
 | 
					    expose:
 | 
				
			||||||
 | 
					      - "3001"
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ../microservices:/usr/src/app
 | 
				
			||||||
 | 
					      - ${UPLOAD_LOCATION}:/usr/src/app/upload
 | 
				
			||||||
 | 
					      - /usr/src/app/node_modules
 | 
				
			||||||
 | 
					    env_file:
 | 
				
			||||||
 | 
					      - .env
 | 
				
			||||||
 | 
					    depends_on:
 | 
				
			||||||
 | 
					      - database
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - immich_network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  redis:
 | 
					  redis:
 | 
				
			||||||
    container_name: immich_redis
 | 
					    container_name: immich_redis
 | 
				
			||||||
    image: redis:6.2
 | 
					    image: redis:6.2
 | 
				
			||||||
@ -61,26 +82,26 @@ services:
 | 
				
			|||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - immich_server
 | 
					      - immich_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  immich_tf_fastapi:
 | 
					  # immich_tf_fastapi:
 | 
				
			||||||
    container_name: immich_tf_fastapi
 | 
					  #   container_name: immich_tf_fastapi
 | 
				
			||||||
    image: tensor_flow_fastapi:1.0.0
 | 
					  #   image: tensor_flow_fastapi:1.0.0
 | 
				
			||||||
    restart: always
 | 
					  #   restart: always
 | 
				
			||||||
    command: uvicorn app.main:app --proxy-headers --host 0.0.0.0 --port 8000 --reload
 | 
					  #   command: uvicorn app.main:app --proxy-headers --host 0.0.0.0 --port 8000 --reload
 | 
				
			||||||
    build:
 | 
					  #   build:
 | 
				
			||||||
      context: ../machine_learning
 | 
					  #     context: ../machine_learning
 | 
				
			||||||
      target: cpu
 | 
					  #     target: cpu
 | 
				
			||||||
      dockerfile: ../machine_learning/Dockerfile
 | 
					  #     dockerfile: ../machine_learning/Dockerfile
 | 
				
			||||||
    volumes:
 | 
					  #   volumes:
 | 
				
			||||||
      - ../machine_learning/app:/code/app
 | 
					  #     - ../machine_learning/app:/code/app
 | 
				
			||||||
      - ${UPLOAD_LOCATION}:/code/app/upload
 | 
					  #     - ${UPLOAD_LOCATION}:/code/app/upload
 | 
				
			||||||
    ports:
 | 
					  #   ports:
 | 
				
			||||||
      - 2285:8000
 | 
					  #     - 2285:8000
 | 
				
			||||||
    expose:
 | 
					  #   expose:
 | 
				
			||||||
      - "8000"
 | 
					  #     - "8000"
 | 
				
			||||||
    depends_on:
 | 
					  #   depends_on:
 | 
				
			||||||
      - database
 | 
					  #     - database
 | 
				
			||||||
    networks:
 | 
					  #   networks:
 | 
				
			||||||
      - immich_network
 | 
					  #     - immich_network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
networks:
 | 
					networks:
 | 
				
			||||||
  immich_network:
 | 
					  immich_network:
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ server {
 | 
				
			|||||||
  client_max_body_size 50000M;
 | 
					  client_max_body_size 50000M;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  listen 80;
 | 
					  listen 80;
 | 
				
			||||||
 | 
					  access_log off;
 | 
				
			||||||
  location / {
 | 
					  location / {
 | 
				
			||||||
    proxy_buffering off;
 | 
					    proxy_buffering off;
 | 
				
			||||||
    proxy_buffer_size 16k;
 | 
					    proxy_buffer_size 16k;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								microservices/.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								microservices/.dockerignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					node_modules/
 | 
				
			||||||
 | 
					upload/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								microservices/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								microservices/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					##################################
 | 
				
			||||||
 | 
					# DEVELOPMENT
 | 
				
			||||||
 | 
					##################################
 | 
				
			||||||
 | 
					FROM node:16-bullseye-slim AS development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG DEBIAN_FRONTEND=noninteractive
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY package.json package-lock.json ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update
 | 
				
			||||||
 | 
					RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#################################
 | 
				
			||||||
 | 
					# PRODUCTION
 | 
				
			||||||
 | 
					#################################
 | 
				
			||||||
 | 
					FROM node:16-bullseye-slim AS production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG DEBIAN_FRONTEND=noninteractive
 | 
				
			||||||
 | 
					ARG NODE_ENV=production
 | 
				
			||||||
 | 
					ENV NODE_ENV=${NODE_ENV}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY package.json package-lock.json ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update
 | 
				
			||||||
 | 
					RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN npm install --only=production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY --from=development /usr/src/app/dist ./dist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["node", "dist/main"]
 | 
				
			||||||
@ -1,73 +1,4 @@
 | 
				
			|||||||
<p align="center">
 | 
					 | 
				
			||||||
  <a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
 | 
					 | 
				
			||||||
</p>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
 | 
					# Microservices for Immich
 | 
				
			||||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
 | 
					## Image Classifier
 | 
				
			||||||
    <p align="center">
 | 
					 | 
				
			||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
 | 
					 | 
				
			||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
 | 
					 | 
				
			||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
 | 
					 | 
				
			||||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
 | 
					 | 
				
			||||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
 | 
					 | 
				
			||||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
 | 
					 | 
				
			||||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
 | 
					 | 
				
			||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
 | 
					 | 
				
			||||||
  <a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
 | 
					 | 
				
			||||||
    <a href="https://opencollective.com/nest#sponsor"  target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
 | 
					 | 
				
			||||||
  <a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
 | 
					 | 
				
			||||||
</p>
 | 
					 | 
				
			||||||
  <!--[](https://opencollective.com/nest#backer)
 | 
					 | 
				
			||||||
  [](https://opencollective.com/nest#sponsor)-->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Description
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Installation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
$ npm install
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Running the app
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
# development
 | 
					 | 
				
			||||||
$ npm run start
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# watch mode
 | 
					 | 
				
			||||||
$ npm run start:dev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# production mode
 | 
					 | 
				
			||||||
$ npm run start:prod
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Test
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
# unit tests
 | 
					 | 
				
			||||||
$ npm run test
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# e2e tests
 | 
					 | 
				
			||||||
$ npm run test:e2e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# test coverage
 | 
					 | 
				
			||||||
$ npm run test:cov
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Support
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Stay in touch
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
 | 
					 | 
				
			||||||
- Website - [https://nestjs.com](https://nestjs.com/)
 | 
					 | 
				
			||||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## License
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Nest is [MIT licensed](LICENSE).
 | 
					 | 
				
			||||||
							
								
								
									
										10979
									
								
								microservices/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10979
									
								
								microservices/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -23,13 +23,25 @@
 | 
				
			|||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@nestjs/common": "^8.0.0",
 | 
					    "@nestjs/common": "^8.0.0",
 | 
				
			||||||
    "@nestjs/core": "^8.0.0",
 | 
					    "@nestjs/core": "^8.0.0",
 | 
				
			||||||
 | 
					    "@nestjs/mapped-types": "^1.0.1",
 | 
				
			||||||
    "@nestjs/platform-express": "^8.0.0",
 | 
					    "@nestjs/platform-express": "^8.0.0",
 | 
				
			||||||
 | 
					    "@nestjs/typeorm": "^8.0.3",
 | 
				
			||||||
 | 
					    "@tensorflow-models/coco-ssd": "^2.2.2",
 | 
				
			||||||
 | 
					    "@tensorflow-models/mobilenet": "^2.1.0",
 | 
				
			||||||
 | 
					    "@tensorflow/tfjs": "^3.15.0",
 | 
				
			||||||
 | 
					    "@tensorflow/tfjs-converter": "^3.15.0",
 | 
				
			||||||
 | 
					    "@tensorflow/tfjs-core": "^3.15.0",
 | 
				
			||||||
 | 
					    "@tensorflow/tfjs-node": "^3.15.0",
 | 
				
			||||||
 | 
					    "@tensorflow/tfjs-node-gpu": "^3.15.0",
 | 
				
			||||||
 | 
					    "@trpc/server": "^9.20.3",
 | 
				
			||||||
 | 
					    "pg": "^8.7.3",
 | 
				
			||||||
    "reflect-metadata": "^0.1.13",
 | 
					    "reflect-metadata": "^0.1.13",
 | 
				
			||||||
    "rimraf": "^3.0.2",
 | 
					    "rimraf": "^3.0.2",
 | 
				
			||||||
    "rxjs": "^7.2.0"
 | 
					    "rxjs": "^7.2.0",
 | 
				
			||||||
 | 
					    "typeorm": "^0.2.45"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@nestjs/cli": "^8.0.0",
 | 
					    "@nestjs/cli": "^8.2.4",
 | 
				
			||||||
    "@nestjs/schematics": "^8.0.0",
 | 
					    "@nestjs/schematics": "^8.0.0",
 | 
				
			||||||
    "@nestjs/testing": "^8.0.0",
 | 
					    "@nestjs/testing": "^8.0.0",
 | 
				
			||||||
    "@types/express": "^4.17.13",
 | 
					    "@types/express": "^4.17.13",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +0,0 @@
 | 
				
			|||||||
import { Controller, Get } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { AppService } from './app.service';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Controller()
 | 
					 | 
				
			||||||
export class AppController {
 | 
					 | 
				
			||||||
  constructor(private readonly appService: AppService) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Get()
 | 
					 | 
				
			||||||
  getHello(): string {
 | 
					 | 
				
			||||||
    return this.appService.getHello();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,10 +1,16 @@
 | 
				
			|||||||
import { Module } from '@nestjs/common';
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
import { AppController } from './app.controller';
 | 
					import { ImageClassifierModule } from './image-classifier/image-classifier.module';
 | 
				
			||||||
import { AppService } from './app.service';
 | 
					import { databaseConfig } from './config/database.config';
 | 
				
			||||||
 | 
					import { TypeOrmModule } from '@nestjs/typeorm';
 | 
				
			||||||
 | 
					import { ObjectDetectionModule } from './object-detection/object-detection.module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [],
 | 
					  imports: [
 | 
				
			||||||
  controllers: [AppController],
 | 
					    TypeOrmModule.forRoot(databaseConfig),
 | 
				
			||||||
  providers: [AppService],
 | 
					    ImageClassifierModule,
 | 
				
			||||||
 | 
					    ObjectDetectionModule,
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  controllers: [],
 | 
				
			||||||
 | 
					  providers: [],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AppModule {}
 | 
					export class AppModule {}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +0,0 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable()
 | 
					 | 
				
			||||||
export class AppService {
 | 
					 | 
				
			||||||
  getHello(): string {
 | 
					 | 
				
			||||||
    console.log('Hello World 123');
 | 
					 | 
				
			||||||
    return 'Hello World!';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								microservices/src/config/database.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								microservices/src/config/database.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { TypeOrmModuleOptions } from '@nestjs/typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const databaseConfig: TypeOrmModuleOptions = {
 | 
				
			||||||
 | 
					  type: 'postgres',
 | 
				
			||||||
 | 
					  host: 'immich_postgres',
 | 
				
			||||||
 | 
					  port: 5432,
 | 
				
			||||||
 | 
					  username: process.env.DB_USERNAME,
 | 
				
			||||||
 | 
					  password: process.env.DB_PASSWORD,
 | 
				
			||||||
 | 
					  database: process.env.DB_DATABASE_NAME,
 | 
				
			||||||
 | 
					  synchronize: false,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { Body, Controller, Post } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { ImageClassifierService } from './image-classifier.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Controller('image-classifier')
 | 
				
			||||||
 | 
					export class ImageClassifierController {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private readonly imageClassifierService: ImageClassifierService,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Post('/tagImage')
 | 
				
			||||||
 | 
					  async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
 | 
				
			||||||
 | 
					    return await this.imageClassifierService.tagImage(thumbnailPath);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { ImageClassifierService } from './image-classifier.service';
 | 
				
			||||||
 | 
					import { ImageClassifierController } from './image-classifier.controller';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  controllers: [ImageClassifierController],
 | 
				
			||||||
 | 
					  providers: [ImageClassifierService],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class ImageClassifierModule {}
 | 
				
			||||||
@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					import { Injectable, Logger } from '@nestjs/common';
 | 
				
			||||||
 | 
					import * as mobilenet from '@tensorflow-models/mobilenet';
 | 
				
			||||||
 | 
					import * as cocoSsd from '@tensorflow-models/coco-ssd';
 | 
				
			||||||
 | 
					import * as tf from '@tensorflow/tfjs-node';
 | 
				
			||||||
 | 
					import * as fs from 'fs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class ImageClassifierService {
 | 
				
			||||||
 | 
					  private readonly MOBILENET_VERSION = 2;
 | 
				
			||||||
 | 
					  private readonly MOBILENET_ALPHA = 1.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private mobileNetModel: mobilenet.MobileNet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    Logger.log(
 | 
				
			||||||
 | 
					      `Running Node TensorFlow Version : ${tf.version['tfjs']}`,
 | 
				
			||||||
 | 
					      'ImageClassifier',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    mobilenet
 | 
				
			||||||
 | 
					      .load({
 | 
				
			||||||
 | 
					        version: this.MOBILENET_VERSION,
 | 
				
			||||||
 | 
					        alpha: this.MOBILENET_ALPHA,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .then((mobilenetModel) => (this.mobileNetModel = mobilenetModel));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async tagImage(thumbnailPath: string) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const isExist = fs.existsSync(thumbnailPath);
 | 
				
			||||||
 | 
					      if (isExist) {
 | 
				
			||||||
 | 
					        const tags = [];
 | 
				
			||||||
 | 
					        const image = fs.readFileSync(thumbnailPath);
 | 
				
			||||||
 | 
					        const decodedImage = tf.node.decodeImage(image, 3) as tf.Tensor3D;
 | 
				
			||||||
 | 
					        const predictions = await this.mobileNetModel.classify(decodedImage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const prediction of predictions) {
 | 
				
			||||||
 | 
					          if (prediction.probability >= 0.1) {
 | 
				
			||||||
 | 
					            tags.push(...prediction.className.split(',').map((e) => e.trim()));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return tags;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.log('Error reading file ', e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,15 +1,10 @@
 | 
				
			|||||||
import { NestFactory } from '@nestjs/core';
 | 
					import { NestFactory } from '@nestjs/core';
 | 
				
			||||||
import { AppModule } from './app.module';
 | 
					import { AppModule } from './app.module';
 | 
				
			||||||
import { AppService } from './app.service';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function bootstrap() {
 | 
					async function bootstrap() {
 | 
				
			||||||
  const app = await NestFactory.createApplicationContext(AppModule);
 | 
					  const app = await NestFactory.create(AppModule);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const appService = app.get(AppService);
 | 
					  await app.listen(3001);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  appService.getHello();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  await app.close();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bootstrap();
 | 
					bootstrap();
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { Body, Controller, Post } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { ObjectDetectionService } from './object-detection.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Controller('object-detection')
 | 
				
			||||||
 | 
					export class ObjectDetectionController {
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private readonly objectDetectionService: ObjectDetectionService,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Post('/detectObject')
 | 
				
			||||||
 | 
					  async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
 | 
				
			||||||
 | 
					    return await this.objectDetectionService.detectObject(thumbnailPath);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { ObjectDetectionService } from './object-detection.service';
 | 
				
			||||||
 | 
					import { ObjectDetectionController } from './object-detection.controller';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Module({
 | 
				
			||||||
 | 
					  controllers: [ObjectDetectionController],
 | 
				
			||||||
 | 
					  providers: [ObjectDetectionService],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class ObjectDetectionModule {}
 | 
				
			||||||
@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import { Injectable, Logger } from '@nestjs/common';
 | 
				
			||||||
 | 
					import * as cocoSsd from '@tensorflow-models/coco-ssd';
 | 
				
			||||||
 | 
					import * as tf from '@tensorflow/tfjs-node';
 | 
				
			||||||
 | 
					import * as fs from 'fs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class ObjectDetectionService {
 | 
				
			||||||
 | 
					  private cocoSsdModel: cocoSsd.ObjectDetection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    Logger.log(
 | 
				
			||||||
 | 
					      `Running Node TensorFlow Version : ${tf.version['tfjs']}`,
 | 
				
			||||||
 | 
					      'ObjectDetection',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    cocoSsd.load().then((model) => (this.cocoSsdModel = model));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  async detectObject(thumbnailPath: string) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const isExist = fs.existsSync(thumbnailPath);
 | 
				
			||||||
 | 
					      if (isExist) {
 | 
				
			||||||
 | 
					        const tags = new Set();
 | 
				
			||||||
 | 
					        const image = fs.readFileSync(thumbnailPath);
 | 
				
			||||||
 | 
					        const decodedImage = tf.node.decodeImage(image, 3) as tf.Tensor3D;
 | 
				
			||||||
 | 
					        const predictions = await this.cocoSsdModel.detect(decodedImage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const result of predictions) {
 | 
				
			||||||
 | 
					          if (result.score > 0.5) {
 | 
				
			||||||
 | 
					            tags.add(result.class);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return [...tags];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.log('Error reading file ', e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
					import { Test, TestingModule } from '@nestjs/testing';
 | 
				
			||||||
import { INestApplication } from '@nestjs/common';
 | 
					import { INestApplication } from '@nestjs/common';
 | 
				
			||||||
import * as request from 'supertest';
 | 
					import * as request from 'supertest';
 | 
				
			||||||
import { AppModule } from './../src/app.module';
 | 
					import { AppModule } from '../src/app.module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// End to End test
 | 
				
			||||||
describe('AppController (e2e)', () => {
 | 
					describe('AppController (e2e)', () => {
 | 
				
			||||||
  let app: INestApplication;
 | 
					  let app: INestApplication;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -79,4 +79,4 @@ SPEC CHECKSUMS:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
PODFILE CHECKSUM: 05c3056158482c567a3e0cdab1351ceeee238a07
 | 
					PODFILE CHECKSUM: 05c3056158482c567a3e0cdab1351ceeee238a07
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COCOAPODS: 1.10.1
 | 
					COCOAPODS: 1.11.3
 | 
				
			||||||
 | 
				
			|||||||
@ -341,7 +341,7 @@
 | 
				
			|||||||
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 | 
									GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 | 
				
			||||||
				GCC_WARN_UNUSED_FUNCTION = YES;
 | 
									GCC_WARN_UNUSED_FUNCTION = YES;
 | 
				
			||||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
									GCC_WARN_UNUSED_VARIABLE = YES;
 | 
				
			||||||
				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 | 
									IPHONEOS_DEPLOYMENT_TARGET = 11.0;
 | 
				
			||||||
				MTL_ENABLE_DEBUG_INFO = NO;
 | 
									MTL_ENABLE_DEBUG_INFO = NO;
 | 
				
			||||||
				NEW_SETTING = "";
 | 
									NEW_SETTING = "";
 | 
				
			||||||
				SDKROOT = iphoneos;
 | 
									SDKROOT = iphoneos;
 | 
				
			||||||
@ -425,7 +425,7 @@
 | 
				
			|||||||
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 | 
									GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 | 
				
			||||||
				GCC_WARN_UNUSED_FUNCTION = YES;
 | 
									GCC_WARN_UNUSED_FUNCTION = YES;
 | 
				
			||||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
									GCC_WARN_UNUSED_VARIABLE = YES;
 | 
				
			||||||
				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 | 
									IPHONEOS_DEPLOYMENT_TARGET = 11.0;
 | 
				
			||||||
				MTL_ENABLE_DEBUG_INFO = YES;
 | 
									MTL_ENABLE_DEBUG_INFO = YES;
 | 
				
			||||||
				NEW_SETTING = "";
 | 
									NEW_SETTING = "";
 | 
				
			||||||
				ONLY_ACTIVE_ARCH = YES;
 | 
									ONLY_ACTIVE_ARCH = YES;
 | 
				
			||||||
@ -475,7 +475,7 @@
 | 
				
			|||||||
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 | 
									GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 | 
				
			||||||
				GCC_WARN_UNUSED_FUNCTION = YES;
 | 
									GCC_WARN_UNUSED_FUNCTION = YES;
 | 
				
			||||||
				GCC_WARN_UNUSED_VARIABLE = YES;
 | 
									GCC_WARN_UNUSED_VARIABLE = YES;
 | 
				
			||||||
				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
 | 
									IPHONEOS_DEPLOYMENT_TARGET = 11.0;
 | 
				
			||||||
				MTL_ENABLE_DEBUG_INFO = NO;
 | 
									MTL_ENABLE_DEBUG_INFO = NO;
 | 
				
			||||||
				NEW_SETTING = "";
 | 
									NEW_SETTING = "";
 | 
				
			||||||
				SDKROOT = iphoneos;
 | 
									SDKROOT = iphoneos;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ class LoginForm extends HookConsumerWidget {
 | 
				
			|||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    final usernameController = useTextEditingController(text: 'testuser@email.com');
 | 
					    final usernameController = useTextEditingController(text: 'testuser@email.com');
 | 
				
			||||||
    final passwordController = useTextEditingController(text: 'password');
 | 
					    final passwordController = useTextEditingController(text: 'password');
 | 
				
			||||||
    final serverEndpointController = useTextEditingController(text: 'http://192.168.1.103:2283');
 | 
					    final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216:2283');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Center(
 | 
					    return Center(
 | 
				
			||||||
      child: ConstrainedBox(
 | 
					      child: ConstrainedBox(
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										80
									
								
								mobile/lib/modules/search/models/curated_object.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								mobile/lib/modules/search/models/curated_object.model.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					import 'dart:convert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CuratedObject {
 | 
				
			||||||
 | 
					  final String id;
 | 
				
			||||||
 | 
					  final String object;
 | 
				
			||||||
 | 
					  final String resizePath;
 | 
				
			||||||
 | 
					  final String deviceAssetId;
 | 
				
			||||||
 | 
					  final String deviceId;
 | 
				
			||||||
 | 
					  CuratedObject({
 | 
				
			||||||
 | 
					    required this.id,
 | 
				
			||||||
 | 
					    required this.object,
 | 
				
			||||||
 | 
					    required this.resizePath,
 | 
				
			||||||
 | 
					    required this.deviceAssetId,
 | 
				
			||||||
 | 
					    required this.deviceId,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CuratedObject copyWith({
 | 
				
			||||||
 | 
					    String? id,
 | 
				
			||||||
 | 
					    String? object,
 | 
				
			||||||
 | 
					    String? resizePath,
 | 
				
			||||||
 | 
					    String? deviceAssetId,
 | 
				
			||||||
 | 
					    String? deviceId,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    return CuratedObject(
 | 
				
			||||||
 | 
					      id: id ?? this.id,
 | 
				
			||||||
 | 
					      object: object ?? this.object,
 | 
				
			||||||
 | 
					      resizePath: resizePath ?? this.resizePath,
 | 
				
			||||||
 | 
					      deviceAssetId: deviceAssetId ?? this.deviceAssetId,
 | 
				
			||||||
 | 
					      deviceId: deviceId ?? this.deviceId,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Map<String, dynamic> toMap() {
 | 
				
			||||||
 | 
					    final result = <String, dynamic>{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result.addAll({'id': id});
 | 
				
			||||||
 | 
					    result.addAll({'object': object});
 | 
				
			||||||
 | 
					    result.addAll({'resizePath': resizePath});
 | 
				
			||||||
 | 
					    result.addAll({'deviceAssetId': deviceAssetId});
 | 
				
			||||||
 | 
					    result.addAll({'deviceId': deviceId});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory CuratedObject.fromMap(Map<String, dynamic> map) {
 | 
				
			||||||
 | 
					    return CuratedObject(
 | 
				
			||||||
 | 
					      id: map['id'] ?? '',
 | 
				
			||||||
 | 
					      object: map['object'] ?? '',
 | 
				
			||||||
 | 
					      resizePath: map['resizePath'] ?? '',
 | 
				
			||||||
 | 
					      deviceAssetId: map['deviceAssetId'] ?? '',
 | 
				
			||||||
 | 
					      deviceId: map['deviceId'] ?? '',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String toJson() => json.encode(toMap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory CuratedObject.fromJson(String source) => CuratedObject.fromMap(json.decode(source));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String toString() {
 | 
				
			||||||
 | 
					    return 'CuratedObject(id: $id, object: $object, resizePath: $resizePath, deviceAssetId: $deviceAssetId, deviceId: $deviceId)';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool operator ==(Object other) {
 | 
				
			||||||
 | 
					    if (identical(this, other)) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return other is CuratedObject &&
 | 
				
			||||||
 | 
					        other.id == id &&
 | 
				
			||||||
 | 
					        other.object == object &&
 | 
				
			||||||
 | 
					        other.resizePath == resizePath &&
 | 
				
			||||||
 | 
					        other.deviceAssetId == deviceAssetId &&
 | 
				
			||||||
 | 
					        other.deviceId == deviceId;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  int get hashCode {
 | 
				
			||||||
 | 
					    return id.hashCode ^ object.hashCode ^ resizePath.hashCode ^ deviceAssetId.hashCode ^ deviceId.hashCode;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
 | 
					import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/models/search_page_state.model.dart';
 | 
					import 'package:immich_mobile/modules/search/models/search_page_state.model.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:immich_mobile/modules/search/services/search.service.dart';
 | 
					import 'package:immich_mobile/modules/search/services/search.service.dart';
 | 
				
			||||||
@ -64,3 +65,14 @@ final getCuratedLocationProvider = FutureProvider.autoDispose<List<CuratedLocati
 | 
				
			|||||||
    return [];
 | 
					    return [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final getCuratedObjectProvider = FutureProvider.autoDispose<List<CuratedObject>>((ref) async {
 | 
				
			||||||
 | 
					  final SearchService _searchService = SearchService();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var curatedObject = await _searchService.getCuratedObjects();
 | 
				
			||||||
 | 
					  if (curatedObject != null) {
 | 
				
			||||||
 | 
					    return curatedObject;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ import 'dart:convert';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
 | 
					import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 | 
					import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/services/network.service.dart';
 | 
					import 'package:immich_mobile/shared/services/network.service.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,4 +53,19 @@ class SearchService {
 | 
				
			|||||||
      throw Error();
 | 
					      throw Error();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<List<CuratedObject>?> getCuratedObjects() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      var res = await _networkService.getRequest(url: "asset/allObjects");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      List<dynamic> decodedData = jsonDecode(res.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      List<CuratedObject> result = List.from(decodedData.map((a) => CuratedObject.fromMap(a)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return result;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      debugPrint("[ERROR] [CuratedObject] ${e.toString()}");
 | 
				
			||||||
 | 
					      throw Error();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -6,10 +6,12 @@ import 'package:hive_flutter/hive_flutter.dart';
 | 
				
			|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/constants/hive_box.dart';
 | 
					import 'package:immich_mobile/constants/hive_box.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
 | 
					import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
 | 
					import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/ui/search_bar.dart';
 | 
					import 'package:immich_mobile/modules/search/ui/search_bar.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
 | 
					import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
 | 
				
			||||||
import 'package:immich_mobile/routing/router.dart';
 | 
					import 'package:immich_mobile/routing/router.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/utils/capitalize_first_letter.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ignore: must_be_immutable
 | 
					// ignore: must_be_immutable
 | 
				
			||||||
class SearchPage extends HookConsumerWidget {
 | 
					class SearchPage extends HookConsumerWidget {
 | 
				
			||||||
@ -22,6 +24,7 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
    var box = Hive.box(userInfoBox);
 | 
					    var box = Hive.box(userInfoBox);
 | 
				
			||||||
    final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
 | 
					    final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
 | 
				
			||||||
    AsyncValue<List<CuratedLocation>> curatedLocation = ref.watch(getCuratedLocationProvider);
 | 
					    AsyncValue<List<CuratedLocation>> curatedLocation = ref.watch(getCuratedLocationProvider);
 | 
				
			||||||
 | 
					    AsyncValue<List<CuratedObject>> curatedObjects = ref.watch(getCuratedObjectProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() {
 | 
					    useEffect(() {
 | 
				
			||||||
      searchFocusNode = FocusNode();
 | 
					      searchFocusNode = FocusNode();
 | 
				
			||||||
@ -82,6 +85,54 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _buildThings() {
 | 
				
			||||||
 | 
					      return curatedObjects.when(
 | 
				
			||||||
 | 
					        loading: () => const CircularProgressIndicator(),
 | 
				
			||||||
 | 
					        error: (err, stack) => Text('Error: $err'),
 | 
				
			||||||
 | 
					        data: (objects) {
 | 
				
			||||||
 | 
					          return objects.isNotEmpty
 | 
				
			||||||
 | 
					              ? SizedBox(
 | 
				
			||||||
 | 
					                  height: MediaQuery.of(context).size.width / 3,
 | 
				
			||||||
 | 
					                  child: ListView.builder(
 | 
				
			||||||
 | 
					                    padding: const EdgeInsets.only(left: 16),
 | 
				
			||||||
 | 
					                    scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					                    itemCount: curatedObjects.value?.length,
 | 
				
			||||||
 | 
					                    itemBuilder: ((context, index) {
 | 
				
			||||||
 | 
					                      CuratedObject curatedObjectInfo = objects[index];
 | 
				
			||||||
 | 
					                      var thumbnailRequestUrl =
 | 
				
			||||||
 | 
					                          '${box.get(serverEndpointKey)}/asset/file?aid=${curatedObjectInfo.deviceAssetId}&did=${curatedObjectInfo.deviceId}&isThumb=true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                      return ThumbnailWithInfo(
 | 
				
			||||||
 | 
					                        imageUrl: thumbnailRequestUrl,
 | 
				
			||||||
 | 
					                        textInfo: curatedObjectInfo.object,
 | 
				
			||||||
 | 
					                        onTap: () {
 | 
				
			||||||
 | 
					                          AutoRouter.of(context)
 | 
				
			||||||
 | 
					                              .push(SearchResultRoute(searchTerm: curatedObjectInfo.object.capitalizeFirstLetter()));
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              : SizedBox(
 | 
				
			||||||
 | 
					                  height: MediaQuery.of(context).size.width / 3,
 | 
				
			||||||
 | 
					                  child: ListView.builder(
 | 
				
			||||||
 | 
					                    padding: const EdgeInsets.only(left: 16),
 | 
				
			||||||
 | 
					                    scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					                    itemCount: 1,
 | 
				
			||||||
 | 
					                    itemBuilder: ((context, index) {
 | 
				
			||||||
 | 
					                      return ThumbnailWithInfo(
 | 
				
			||||||
 | 
					                        imageUrl:
 | 
				
			||||||
 | 
					                            'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
 | 
				
			||||||
 | 
					                        textInfo: 'No Object Info Available',
 | 
				
			||||||
 | 
					                        onTap: () {},
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: SearchBar(
 | 
					      appBar: SearchBar(
 | 
				
			||||||
        searchFocusNode: searchFocusNode,
 | 
					        searchFocusNode: searchFocusNode,
 | 
				
			||||||
@ -104,6 +155,14 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                _buildPlaces(),
 | 
					                _buildPlaces(),
 | 
				
			||||||
 | 
					                const Padding(
 | 
				
			||||||
 | 
					                  padding: EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					                  child: Text(
 | 
				
			||||||
 | 
					                    "Things",
 | 
				
			||||||
 | 
					                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                _buildThings()
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            isSearchEnabled ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(),
 | 
					            isSearchEnabled ? SearchSuggestionList(onSubmitted: _onSearchSubmitted) : Container(),
 | 
				
			||||||
@ -160,7 +219,7 @@ class ThumbnailWithInfo extends StatelessWidget {
 | 
				
			|||||||
                child: SizedBox(
 | 
					                child: SizedBox(
 | 
				
			||||||
                  width: MediaQuery.of(context).size.width / 3,
 | 
					                  width: MediaQuery.of(context).size.width / 3,
 | 
				
			||||||
                  child: Text(
 | 
					                  child: Text(
 | 
				
			||||||
                    textInfo,
 | 
					                    textInfo.capitalizeFirstLetter(),
 | 
				
			||||||
                    style: const TextStyle(
 | 
					                    style: const TextStyle(
 | 
				
			||||||
                      color: Colors.white,
 | 
					                      color: Colors.white,
 | 
				
			||||||
                      fontWeight: FontWeight.bold,
 | 
					                      fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ class TabNavigationObserver extends AutoRouterObserver {
 | 
				
			|||||||
    if (route.name == 'SearchRoute') {
 | 
					    if (route.name == 'SearchRoute') {
 | 
				
			||||||
      // Refresh Location State
 | 
					      // Refresh Location State
 | 
				
			||||||
      ref.refresh(getCuratedLocationProvider);
 | 
					      ref.refresh(getCuratedLocationProvider);
 | 
				
			||||||
 | 
					      ref.refresh(getCuratedObjectProvider);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ref.watch(serverInfoProvider.notifier).getServerVersion();
 | 
					    ref.watch(serverInfoProvider.notifier).getServerVersion();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								mobile/lib/utils/capitalize_first_letter.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mobile/lib/utils/capitalize_first_letter.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					extension StringExtension on String {
 | 
				
			||||||
 | 
					  String capitalizeFirstLetter() {
 | 
				
			||||||
 | 
					    return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										90
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,12 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "immich",
 | 
					  "name": "immich",
 | 
				
			||||||
  "version": "0.0.1",
 | 
					  "version": "1.3.2",
 | 
				
			||||||
  "lockfileVersion": 2,
 | 
					  "lockfileVersion": 2,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "immich",
 | 
					      "name": "immich",
 | 
				
			||||||
      "version": "0.0.1",
 | 
					      "version": "1.3.2",
 | 
				
			||||||
      "license": "UNLICENSED",
 | 
					      "license": "UNLICENSED",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@mapbox/mapbox-sdk": "^0.13.3",
 | 
					        "@mapbox/mapbox-sdk": "^0.13.3",
 | 
				
			||||||
@ -1547,6 +1547,66 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@nestjs/microservices": {
 | 
				
			||||||
 | 
					      "version": "8.4.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-8.4.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/ZT5wo1s65J9Cqp2g5eNrYO34VH7/qUkDu4jJyZCT61I9UqpO49J3+1YIAHfmJJzHcrenjgt1sBtlFhwPR3Lgg==",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "iterare": "1.2.1",
 | 
				
			||||||
 | 
					        "json-socket": "0.3.0",
 | 
				
			||||||
 | 
					        "tslib": "2.3.1"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/nest"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@grpc/grpc-js": "*",
 | 
				
			||||||
 | 
					        "@nestjs/common": "^8.0.0",
 | 
				
			||||||
 | 
					        "@nestjs/core": "^8.0.0",
 | 
				
			||||||
 | 
					        "@nestjs/websockets": "^8.0.0",
 | 
				
			||||||
 | 
					        "amqp-connection-manager": "*",
 | 
				
			||||||
 | 
					        "amqplib": "*",
 | 
				
			||||||
 | 
					        "cache-manager": "*",
 | 
				
			||||||
 | 
					        "kafkajs": "*",
 | 
				
			||||||
 | 
					        "mqtt": "*",
 | 
				
			||||||
 | 
					        "nats": "*",
 | 
				
			||||||
 | 
					        "redis": "*",
 | 
				
			||||||
 | 
					        "reflect-metadata": "^0.1.12",
 | 
				
			||||||
 | 
					        "rxjs": "^7.1.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependenciesMeta": {
 | 
				
			||||||
 | 
					        "@grpc/grpc-js": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "@nestjs/websockets": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "amqp-connection-manager": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "amqplib": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "cache-manager": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "kafkajs": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "mqtt": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "nats": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "redis": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@nestjs/passport": {
 | 
					    "node_modules/@nestjs/passport": {
 | 
				
			||||||
      "version": "8.1.0",
 | 
					      "version": "8.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-8.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-8.1.0.tgz",
 | 
				
			||||||
@ -7053,6 +7113,13 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
 | 
					      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/json-socket": {
 | 
				
			||||||
 | 
					      "version": "0.3.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/json-socket/-/json-socket-0.3.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jc8ZbUnYIWdxERFWQKVgwSLkGSe+kyzvmYxwNaRgx/c8NNyuHes4UHnPM3LUrAFXUx1BhNJ94n1h/KCRlbvV0g==",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/json-stable-stringify-without-jsonify": {
 | 
					    "node_modules/json-stable-stringify-without-jsonify": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
 | 
				
			||||||
@ -11943,6 +12010,18 @@
 | 
				
			|||||||
      "integrity": "sha512-NFvofzSinp00j5rzUd4tf+xi9od6383iY0JP7o0Bnu1fuItAUkWBgc4EKuIQ3D+c2QI3i9pG1kDWAeY27EMGtg==",
 | 
					      "integrity": "sha512-NFvofzSinp00j5rzUd4tf+xi9od6383iY0JP7o0Bnu1fuItAUkWBgc4EKuIQ3D+c2QI3i9pG1kDWAeY27EMGtg==",
 | 
				
			||||||
      "requires": {}
 | 
					      "requires": {}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@nestjs/microservices": {
 | 
				
			||||||
 | 
					      "version": "8.4.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-8.4.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/ZT5wo1s65J9Cqp2g5eNrYO34VH7/qUkDu4jJyZCT61I9UqpO49J3+1YIAHfmJJzHcrenjgt1sBtlFhwPR3Lgg==",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "peer": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "iterare": "1.2.1",
 | 
				
			||||||
 | 
					        "json-socket": "0.3.0",
 | 
				
			||||||
 | 
					        "tslib": "2.3.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@nestjs/passport": {
 | 
					    "@nestjs/passport": {
 | 
				
			||||||
      "version": "8.1.0",
 | 
					      "version": "8.1.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-8.1.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-8.1.0.tgz",
 | 
				
			||||||
@ -16243,6 +16322,13 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
 | 
					      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "json-socket": {
 | 
				
			||||||
 | 
					      "version": "0.3.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/json-socket/-/json-socket-0.3.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jc8ZbUnYIWdxERFWQKVgwSLkGSe+kyzvmYxwNaRgx/c8NNyuHes4UHnPM3LUrAFXUx1BhNJ94n1h/KCRlbvV0g==",
 | 
				
			||||||
 | 
					      "optional": true,
 | 
				
			||||||
 | 
					      "peer": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "json-stable-stringify-without-jsonify": {
 | 
					    "json-stable-stringify-without-jsonify": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -16,13 +16,12 @@ import {
 | 
				
			|||||||
} from '@nestjs/common';
 | 
					} from '@nestjs/common';
 | 
				
			||||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 | 
					import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 | 
				
			||||||
import { AssetService } from './asset.service';
 | 
					import { AssetService } from './asset.service';
 | 
				
			||||||
import { FileFieldsInterceptor, FilesInterceptor } from '@nestjs/platform-express';
 | 
					import { FileFieldsInterceptor } from '@nestjs/platform-express';
 | 
				
			||||||
import { multerOption } from '../../config/multer-option.config';
 | 
					import { multerOption } from '../../config/multer-option.config';
 | 
				
			||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
					import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
				
			||||||
import { CreateAssetDto } from './dto/create-asset.dto';
 | 
					import { CreateAssetDto } from './dto/create-asset.dto';
 | 
				
			||||||
import { ServeFileDto } from './dto/serve-file.dto';
 | 
					import { ServeFileDto } from './dto/serve-file.dto';
 | 
				
			||||||
import { AssetOptimizeService } from '../../modules/image-optimize/image-optimize.service';
 | 
					import { AssetEntity } from './entities/asset.entity';
 | 
				
			||||||
import { AssetEntity, AssetType } from './entities/asset.entity';
 | 
					 | 
				
			||||||
import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
 | 
					import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
 | 
				
			||||||
import { Response as Res } from 'express';
 | 
					import { Response as Res } from 'express';
 | 
				
			||||||
import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto';
 | 
					import { GetNewAssetQueryDto } from './dto/get-new-asset-query.dto';
 | 
				
			||||||
@ -61,6 +60,7 @@ export class AssetController {
 | 
				
			|||||||
      if (uploadFiles.thumbnailData != null) {
 | 
					      if (uploadFiles.thumbnailData != null) {
 | 
				
			||||||
        await this.assetService.updateThumbnailInfo(savedAsset.id, uploadFiles.thumbnailData[0].path);
 | 
					        await this.assetService.updateThumbnailInfo(savedAsset.id, uploadFiles.thumbnailData[0].path);
 | 
				
			||||||
        await this.backgroundTaskService.tagImage(uploadFiles.thumbnailData[0].path, savedAsset);
 | 
					        await this.backgroundTaskService.tagImage(uploadFiles.thumbnailData[0].path, savedAsset);
 | 
				
			||||||
 | 
					        await this.backgroundTaskService.detectObject(uploadFiles.thumbnailData[0].path, savedAsset);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await this.backgroundTaskService.extractExif(savedAsset, file.originalname, file.size);
 | 
					      await this.backgroundTaskService.extractExif(savedAsset, file.originalname, file.size);
 | 
				
			||||||
@ -81,6 +81,11 @@ export class AssetController {
 | 
				
			|||||||
    return this.assetService.serveFile(authUser, query, res, headers);
 | 
					    return this.assetService.serveFile(authUser, query, res, headers);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Get('/allObjects')
 | 
				
			||||||
 | 
					  async getCuratedObject(@GetAuthUser() authUser: AuthUserDto) {
 | 
				
			||||||
 | 
					    return this.assetService.getCuratedObject(authUser);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Get('/allLocation')
 | 
					  @Get('/allLocation')
 | 
				
			||||||
  async getCuratedLocation(@GetAuthUser() authUser: AuthUserDto) {
 | 
					  async getCuratedLocation(@GetAuthUser() authUser: AuthUserDto) {
 | 
				
			||||||
    return this.assetService.getCuratedLocation(authUser);
 | 
					    return this.assetService.getCuratedLocation(authUser);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			|||||||
import { MoreThan, Repository } from 'typeorm';
 | 
					import { MoreThan, Repository } from 'typeorm';
 | 
				
			||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
					import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
				
			||||||
import { CreateAssetDto } from './dto/create-asset.dto';
 | 
					import { CreateAssetDto } from './dto/create-asset.dto';
 | 
				
			||||||
import { UpdateAssetDto } from './dto/update-asset.dto';
 | 
					 | 
				
			||||||
import { AssetEntity, AssetType } from './entities/asset.entity';
 | 
					import { AssetEntity, AssetType } from './entities/asset.entity';
 | 
				
			||||||
import _, { result } from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
 | 
					import { GetAllAssetQueryDto } from './dto/get-all-asset-query.dto';
 | 
				
			||||||
import { GetAllAssetReponseDto } from './dto/get-all-asset-response.dto';
 | 
					import { GetAllAssetReponseDto } from './dto/get-all-asset-response.dto';
 | 
				
			||||||
import { createReadStream, stat } from 'fs';
 | 
					import { createReadStream, stat } from 'fs';
 | 
				
			||||||
@ -44,9 +43,7 @@ export class AssetService {
 | 
				
			|||||||
    asset.duration = assetInfo.duration;
 | 
					    asset.duration = assetInfo.duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const res = await this.assetRepository.save(asset);
 | 
					      return await this.assetRepository.save(asset);
 | 
				
			||||||
 | 
					 | 
				
			||||||
      return res;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      Logger.error(`Error Create New Asset ${e}`, 'createUserAsset');
 | 
					      Logger.error(`Error Create New Asset ${e}`, 'createUserAsset');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -68,13 +65,11 @@ export class AssetService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  public async getAllAssetsNoPagination(authUser: AuthUserDto) {
 | 
					  public async getAllAssetsNoPagination(authUser: AuthUserDto) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const assets = await this.assetRepository
 | 
					      return await this.assetRepository
 | 
				
			||||||
        .createQueryBuilder('a')
 | 
					        .createQueryBuilder('a')
 | 
				
			||||||
        .where('a."userId" = :userId', { userId: authUser.id })
 | 
					        .where('a."userId" = :userId', { userId: authUser.id })
 | 
				
			||||||
        .orderBy('a."createdAt"::date', 'DESC')
 | 
					        .orderBy('a."createdAt"::date', 'DESC')
 | 
				
			||||||
        .getMany();
 | 
					        .getMany();
 | 
				
			||||||
 | 
					 | 
				
			||||||
      return assets;
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      Logger.error(e, 'getAllAssets');
 | 
					      Logger.error(e, 'getAllAssets');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -226,10 +221,10 @@ export class AssetService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async deleteAssetById(authUser: AuthUserDto, assetIds: DeleteAssetDto) {
 | 
					  public async deleteAssetById(authUser: AuthUserDto, assetIds: DeleteAssetDto) {
 | 
				
			||||||
    let result = [];
 | 
					    const result = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const target = assetIds.ids;
 | 
					    const target = assetIds.ids;
 | 
				
			||||||
    for (let assetId of target) {
 | 
					    for (const assetId of target) {
 | 
				
			||||||
      const res = await this.assetRepository.delete({
 | 
					      const res = await this.assetRepository.delete({
 | 
				
			||||||
        id: assetId,
 | 
					        id: assetId,
 | 
				
			||||||
        userId: authUser.id,
 | 
					        userId: authUser.id,
 | 
				
			||||||
@ -251,11 +246,11 @@ export class AssetService {
 | 
				
			|||||||
    return result;
 | 
					    return result;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getAssetSearchTerm(authUser: AuthUserDto): Promise<String[]> {
 | 
					  async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
 | 
				
			||||||
    const possibleSearchTerm = new Set<String>();
 | 
					    const possibleSearchTerm = new Set<string>();
 | 
				
			||||||
    const rows = await this.assetRepository.query(
 | 
					    const rows = await this.assetRepository.query(
 | 
				
			||||||
      `
 | 
					      `
 | 
				
			||||||
      select distinct si.tags, e.orientation, e."lensModel", e.make, e.model , a.type, e.city, e.state, e.country
 | 
					      select distinct si.tags, si.objects, e.orientation, e."lensModel", e.make, e.model , a.type, e.city, e.state, e.country
 | 
				
			||||||
      from assets a
 | 
					      from assets a
 | 
				
			||||||
      left join exif e on a.id = e."assetId"
 | 
					      left join exif e on a.id = e."assetId"
 | 
				
			||||||
      left join smart_info si on a.id = si."assetId"
 | 
					      left join smart_info si on a.id = si."assetId"
 | 
				
			||||||
@ -268,6 +263,9 @@ export class AssetService {
 | 
				
			|||||||
      // tags
 | 
					      // tags
 | 
				
			||||||
      row['tags']?.map((tag) => possibleSearchTerm.add(tag?.toLowerCase()));
 | 
					      row['tags']?.map((tag) => possibleSearchTerm.add(tag?.toLowerCase()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // objects
 | 
				
			||||||
 | 
					      row['objects']?.map((object) => possibleSearchTerm.add(object?.toLowerCase()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // asset's tyoe
 | 
					      // asset's tyoe
 | 
				
			||||||
      possibleSearchTerm.add(row['type']?.toLowerCase());
 | 
					      possibleSearchTerm.add(row['type']?.toLowerCase());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -301,17 +299,16 @@ export class AssetService {
 | 
				
			|||||||
       AND 
 | 
					       AND 
 | 
				
			||||||
       (
 | 
					       (
 | 
				
			||||||
         TO_TSVECTOR('english', ARRAY_TO_STRING(si.tags, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
 | 
					         TO_TSVECTOR('english', ARRAY_TO_STRING(si.tags, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
 | 
				
			||||||
 | 
					         TO_TSVECTOR('english', ARRAY_TO_STRING(si.objects, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
 | 
				
			||||||
         e.exif_text_searchable_column @@ PLAINTO_TSQUERY('english', $2)
 | 
					         e.exif_text_searchable_column @@ PLAINTO_TSQUERY('english', $2)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const rows = await this.assetRepository.query(query, [authUser.id, searchAssetDto.searchTerm]);
 | 
					    return await this.assetRepository.query(query, [authUser.id, searchAssetDto.searchTerm]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    return rows;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getCuratedLocation(authUser: AuthUserDto) {
 | 
					  async getCuratedLocation(authUser: AuthUserDto) {
 | 
				
			||||||
    const rows = await this.assetRepository.query(
 | 
					    return await this.assetRepository.query(
 | 
				
			||||||
      `
 | 
					      `
 | 
				
			||||||
        select distinct on (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
 | 
					        select distinct on (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
 | 
				
			||||||
        from assets a
 | 
					        from assets a
 | 
				
			||||||
@ -322,7 +319,18 @@ export class AssetService {
 | 
				
			|||||||
      `,
 | 
					      `,
 | 
				
			||||||
      [authUser.id],
 | 
					      [authUser.id],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return rows;
 | 
					  async getCuratedObject(authUser: AuthUserDto) {
 | 
				
			||||||
 | 
					    return await this.assetRepository.query(
 | 
				
			||||||
 | 
					      `
 | 
				
			||||||
 | 
					        select distinct on (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
 | 
				
			||||||
 | 
					        from assets a
 | 
				
			||||||
 | 
					        left join smart_info si on a.id = si."assetId"
 | 
				
			||||||
 | 
					        where a."userId" = $1 
 | 
				
			||||||
 | 
					        and si.objects is not null
 | 
				
			||||||
 | 
					      `,
 | 
				
			||||||
 | 
					      [authUser.id],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,9 @@ export class SmartInfoEntity {
 | 
				
			|||||||
  @Column({ type: 'text', array: true, nullable: true })
 | 
					  @Column({ type: 'text', array: true, nullable: true })
 | 
				
			||||||
  tags: string[];
 | 
					  tags: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Column({ type: 'text', array: true, nullable: true })
 | 
				
			||||||
 | 
					  objects: string[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
 | 
					  @OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
 | 
				
			||||||
  @JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
 | 
					  @JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
 | 
				
			||||||
  asset: SmartInfoEntity;
 | 
					  asset: SmartInfoEntity;
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ import { UserModule } from './api-v1/user/user.module';
 | 
				
			|||||||
import { AssetModule } from './api-v1/asset/asset.module';
 | 
					import { AssetModule } from './api-v1/asset/asset.module';
 | 
				
			||||||
import { AuthModule } from './api-v1/auth/auth.module';
 | 
					import { AuthModule } from './api-v1/auth/auth.module';
 | 
				
			||||||
import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module';
 | 
					import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module';
 | 
				
			||||||
import { JwtModule } from '@nestjs/jwt';
 | 
					 | 
				
			||||||
import { DeviceInfoModule } from './api-v1/device-info/device-info.module';
 | 
					import { DeviceInfoModule } from './api-v1/device-info/device-info.module';
 | 
				
			||||||
import { AppLoggerMiddleware } from './middlewares/app-logger.middleware';
 | 
					import { AppLoggerMiddleware } from './middlewares/app-logger.middleware';
 | 
				
			||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
					import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
				
			||||||
@ -26,14 +25,12 @@ import { CommunicationModule } from './api-v1/communication/communication.module
 | 
				
			|||||||
    ImmichJwtModule,
 | 
					    ImmichJwtModule,
 | 
				
			||||||
    DeviceInfoModule,
 | 
					    DeviceInfoModule,
 | 
				
			||||||
    BullModule.forRootAsync({
 | 
					    BullModule.forRootAsync({
 | 
				
			||||||
      imports: [ConfigModule],
 | 
					      useFactory: async () => ({
 | 
				
			||||||
      useFactory: async (configService: ConfigService) => ({
 | 
					 | 
				
			||||||
        redis: {
 | 
					        redis: {
 | 
				
			||||||
          host: 'immich_redis',
 | 
					          host: 'immich_redis',
 | 
				
			||||||
          port: 6379,
 | 
					          port: 6379,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      inject: [ConfigService],
 | 
					 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ImageOptimizeModule,
 | 
					    ImageOptimizeModule,
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { MigrationInterface, QueryRunner } from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class AddObjectColumnToSmartInfo1648317474768
 | 
				
			||||||
 | 
					  implements MigrationInterface
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  public async up(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					    await queryRunner.query(`
 | 
				
			||||||
 | 
					      ALTER TABLE smart_info
 | 
				
			||||||
 | 
					        ADD COLUMN objects text[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    `);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async down(queryRunner: QueryRunner): Promise<void> {
 | 
				
			||||||
 | 
					    await queryRunner.query(`
 | 
				
			||||||
 | 
					      ALTER TABLE smart_info
 | 
				
			||||||
 | 
					        DROP COLUMN objects;
 | 
				
			||||||
 | 
					    `);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -6,7 +6,7 @@ import { AssetEntity } from '../../api-v1/asset/entities/asset.entity';
 | 
				
			|||||||
import { ConfigService } from '@nestjs/config';
 | 
					import { ConfigService } from '@nestjs/config';
 | 
				
			||||||
import exifr from 'exifr';
 | 
					import exifr from 'exifr';
 | 
				
			||||||
import { readFile } from 'fs/promises';
 | 
					import { readFile } from 'fs/promises';
 | 
				
			||||||
import fs, { rmSync } from 'fs';
 | 
					import fs from 'fs';
 | 
				
			||||||
import { Logger } from '@nestjs/common';
 | 
					import { Logger } from '@nestjs/common';
 | 
				
			||||||
import { ExifEntity } from '../../api-v1/asset/entities/exif.entity';
 | 
					import { ExifEntity } from '../../api-v1/asset/entities/exif.entity';
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
@ -114,14 +114,37 @@ export class BackgroundTaskProcessor {
 | 
				
			|||||||
  @Process('tag-image')
 | 
					  @Process('tag-image')
 | 
				
			||||||
  async tagImage(job) {
 | 
					  async tagImage(job) {
 | 
				
			||||||
    const { thumbnailPath, asset }: { thumbnailPath: string; asset: AssetEntity } = job.data;
 | 
					    const { thumbnailPath, asset }: { thumbnailPath: string; asset: AssetEntity } = job.data;
 | 
				
			||||||
    const res = await axios.post('http://immich_tf_fastapi:8000/tagImage', { thumbnail_path: thumbnailPath });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (res.status == 200) {
 | 
					    const res = await axios.post('http://immich_microservices:3001/image-classifier/tagImage', {
 | 
				
			||||||
 | 
					      thumbnailPath: thumbnailPath,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (res.status == 201 && res.data.length > 0) {
 | 
				
			||||||
      const smartInfo = new SmartInfoEntity();
 | 
					      const smartInfo = new SmartInfoEntity();
 | 
				
			||||||
      smartInfo.assetId = asset.id;
 | 
					      smartInfo.assetId = asset.id;
 | 
				
			||||||
      smartInfo.tags = [...res.data];
 | 
					      smartInfo.tags = [...res.data];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await this.smartInfoRepository.save(smartInfo);
 | 
					      await this.smartInfoRepository.upsert(smartInfo, {
 | 
				
			||||||
 | 
					        conflictPaths: ['assetId'],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Process('detect-object')
 | 
				
			||||||
 | 
					  async detectObject(job) {
 | 
				
			||||||
 | 
					    const { thumbnailPath, asset }: { thumbnailPath: string; asset: AssetEntity } = job.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await axios.post('http://immich_microservices:3001/object-detection/detectObject', {
 | 
				
			||||||
 | 
					      thumbnailPath: thumbnailPath,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (res.status == 201 && res.data.length > 0) {
 | 
				
			||||||
 | 
					      const smartInfo = new SmartInfoEntity();
 | 
				
			||||||
 | 
					      smartInfo.assetId = asset.id;
 | 
				
			||||||
 | 
					      smartInfo.objects = [...res.data];
 | 
				
			||||||
 | 
					      await this.smartInfoRepository.upsert(smartInfo, {
 | 
				
			||||||
 | 
					        conflictPaths: ['assetId'],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -43,4 +43,15 @@ export class BackgroundTaskService {
 | 
				
			|||||||
      { jobId: randomUUID() },
 | 
					      { jobId: randomUUID() },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async detectObject(thumbnailPath: string, asset: AssetEntity) {
 | 
				
			||||||
 | 
					    await this.backgroundTaskQueue.add(
 | 
				
			||||||
 | 
					      'detect-object',
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        thumbnailPath,
 | 
				
			||||||
 | 
					        asset,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      { jobId: randomUUID() },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user