Skip to content

Commit

Permalink
Merge branch 'main' into shared_albums
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukasdotcom committed Sep 8, 2024
2 parents 8510ec0 + d1ce9e4 commit 1503ebd
Show file tree
Hide file tree
Showing 33 changed files with 364 additions and 192 deletions.
4 changes: 1 addition & 3 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,4 @@ documentation:
- machine-learning/app/**

changelog:translation:
- changed-files:
- any-glob-to-any-file:
- web/src/lib/i18n/*.json
- head-branch: ['^chore/translations$']
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ For the mobile app, you can use `https://demo.immich.app/api` for the `Server En
| LivePhoto/MotionPhoto backup and playback | Yes | Yes |
| Support 360 degree image display | No | Yes |
| User-defined storage structure | Yes | Yes |
| Public Sharing | No | Yes |
| Public Sharing | Yes | Yes |
| Archive and Favorites | Yes | Yes |
| Global Map | Yes | Yes |
| Partner Sharing | Yes | Yes |
Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ services:

redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
healthcheck:
test: redis-cli ping || exit 1

Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ services:

redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
healthcheck:
test: redis-cli ping || exit 1
restart: always
Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ services:

redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
image: docker.io/redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01
healthcheck:
test: redis-cli ping || exit 1
restart: always
Expand Down
2 changes: 1 addition & 1 deletion e2e/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ services:
- 2285:3001

redis:
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
image: redis:6.2-alpine@sha256:fd1b5400ca24adc2ff77abdf00acb72c3aae85b94e43557ab2606d29a74bfa01

database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
Expand Down
9 changes: 4 additions & 5 deletions machine-learning/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@ FROM prod-cpu AS prod-openvino

RUN apt-get update && \
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-core_1.0.17193.4_amd64.deb && \
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17193.4/intel-igc-opencl_1.0.17193.4_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd-dbgsym_24.26.30049.6_amd64.ddeb && \
wget https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/intel-opencl-icd_24.26.30049.6_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/24.26.30049.6/libigdgmm12_22.3.20_amd64.deb && \
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-core_1.0.17384.11_amd64.deb && \
wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-opencl_1.0.17384.11_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/intel-opencl-icd_24.31.30508.7_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/libigdgmm12_22.4.1_amd64.deb && \
dpkg -i *.deb && \
rm *.deb && \
apt-get remove wget -yqq && \
Expand Down
6 changes: 3 additions & 3 deletions mobile/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
Expand Down Expand Up @@ -543,7 +543,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
Expand Down Expand Up @@ -571,7 +571,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 172;
CURRENT_PROJECT_VERSION = 173;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
Expand Down
4 changes: 2 additions & 2 deletions mobile/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.113.1</string>
<string>1.114.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>172</string>
<string>173</string>
<key>FLTEnableImpeller</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
Expand Down
8 changes: 5 additions & 3 deletions server/src/entities/system-metadata.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ export class SystemMetadataEntity<T extends keyof SystemMetadata = SystemMetadat
}

export type VersionCheckMetadata = { checkedAt: string; releaseVersion: string };
export type SystemFlags = { mountFiles: boolean };

export interface SystemMetadata extends Record<SystemMetadataKey, Record<string, any>> {
[SystemMetadataKey.REVERSE_GEOCODING_STATE]: { lastUpdate?: string; lastImportFileName?: string };
[SystemMetadataKey.FACIAL_RECOGNITION_STATE]: { lastRun?: string };
[SystemMetadataKey.ADMIN_ONBOARDING]: { isOnboarded: boolean };
[SystemMetadataKey.FACIAL_RECOGNITION_STATE]: { lastRun?: string };
[SystemMetadataKey.LICENSE]: { licenseKey: string; activationKey: string; activatedAt: Date };
[SystemMetadataKey.REVERSE_GEOCODING_STATE]: { lastUpdate?: string; lastImportFileName?: string };
[SystemMetadataKey.SYSTEM_CONFIG]: DeepPartial<SystemConfig>;
[SystemMetadataKey.SYSTEM_FLAGS]: SystemFlags;
[SystemMetadataKey.VERSION_CHECK_STATE]: VersionCheckMetadata;
[SystemMetadataKey.LICENSE]: { licenseKey: string; activationKey: string; activatedAt: Date };
}
1 change: 1 addition & 0 deletions server/src/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export enum SystemMetadataKey {
FACIAL_RECOGNITION_STATE = 'facial-recognition-state',
ADMIN_ONBOARDING = 'admin-onboarding',
SYSTEM_CONFIG = 'system-config',
SYSTEM_FLAGS = 'system-flags',
VERSION_CHECK_STATE = 'version-check-state',
LICENSE = 'license',
}
Expand Down
1 change: 1 addition & 0 deletions server/src/interfaces/database.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum VectorIndex {
export enum DatabaseLock {
GeodataImport = 100,
Migrations = 200,
SystemFileMounts = 300,
StorageTemplateMigration = 420,
CLIPDimSize = 512,
LibraryWatch = 1337,
Expand Down
7 changes: 6 additions & 1 deletion server/src/interfaces/event.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type EmitEventMap = {
'asset.tag': [{ assetId: string }];
'asset.untag': [{ assetId: string }];

// session events
'session.delete': [{ sessionId: string }];

// user events
'user.signup': [{ notify: boolean; id: string; tempPassword?: string }];
};
Expand All @@ -43,6 +46,7 @@ export enum ClientEvent {
SERVER_VERSION = 'on_server_version',
CONFIG_UPDATE = 'on_config_update',
NEW_RELEASE = 'on_new_release',
SESSION_DELETE = 'on_session_delete',
}

export interface ClientEventMap {
Expand All @@ -58,6 +62,7 @@ export interface ClientEventMap {
[ClientEvent.SERVER_VERSION]: ServerVersionResponseDto;
[ClientEvent.CONFIG_UPDATE]: Record<string, never>;
[ClientEvent.NEW_RELEASE]: ReleaseNotification;
[ClientEvent.SESSION_DELETE]: string;
}

export enum ServerEvent {
Expand All @@ -77,7 +82,7 @@ export interface IEventRepository {
/**
* Send to connected clients for a specific user
*/
clientSend<E extends keyof ClientEventMap>(event: E, userId: string, data: ClientEventMap[E]): void;
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]): void;
/**
* Send to all connected clients
*/
Expand Down
2 changes: 1 addition & 1 deletion server/src/interfaces/map.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface MapMarker extends ReverseGeocodeResult {

export interface IMapRepository {
init(): Promise<void>;
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult | null>;
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>;
getMapMarkers(ownerIds: string[], albumIds: string[], options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
fetchStyle(url: string): Promise<any>;
}
2 changes: 1 addition & 1 deletion server/src/interfaces/metadata.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface ImmichTags extends Omit<Tags, TagsWithWrongTypes> {

export interface IMetadataRepository {
teardown(): Promise<void>;
readTags(path: string): Promise<ImmichTags | null>;
readTags(path: string): Promise<ImmichTags>;
writeTags(path: string, tags: Partial<Tags>): Promise<void>;
extractBinaryTag(tagName: string, path: string): Promise<Buffer>;
getCountries(userIds: string[]): Promise<Array<string | null>>;
Expand Down
6 changes: 6 additions & 0 deletions server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ async function bootstrapImmichAdmin() {

function bootstrapWorker(name: string) {
console.log(`Starting ${name} worker`);

const worker = name === 'api' ? fork(`./dist/workers/${name}.js`) : new Worker(`./dist/workers/${name}.js`);

worker.on('error', (error) => {
console.error(`${name} worker error: ${error}`);
});

worker.on('exit', (exitCode) => {
if (exitCode !== 0) {
console.error(`${name} worker exited with code ${exitCode}`);
Expand Down
12 changes: 8 additions & 4 deletions server/src/repositories/event.repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
OnGatewayConnection,
Expand Down Expand Up @@ -37,7 +38,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
private server?: Server;

constructor(
private authService: AuthService,
private moduleRef: ModuleRef,
private eventEmitter: EventEmitter2,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
) {
Expand All @@ -62,12 +63,15 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
async handleConnection(client: Socket) {
try {
this.logger.log(`Websocket Connect: ${client.id}`);
const auth = await this.authService.authenticate({
const auth = await this.moduleRef.get(AuthService).authenticate({
headers: client.request.headers,
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: '/api/socket.io' },
});
await client.join(auth.user.id);
if (auth.session) {
await client.join(auth.session.id);
}
this.serverSend(ServerEvent.WEBSOCKET_CONNECT, { userId: auth.user.id });
} catch (error: Error | any) {
this.logger.error(`Websocket connection error: ${error}`, error?.stack);
Expand Down Expand Up @@ -96,8 +100,8 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
}
}

clientSend<E extends keyof ClientEventMap>(event: E, userId: string, data: ClientEventMap[E]) {
this.server?.to(userId).emit(event, data);
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]) {
this.server?.to(room).emit(event, data);
}

clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]) {
Expand Down
4 changes: 2 additions & 2 deletions server/src/repositories/map.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class MapRepository implements IMapRepository {
}
}

async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult | null> {
async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult> {
this.logger.debug(`Request: ${point.latitude},${point.longitude}`);

const response = await this.geodataPlacesRepository
Expand Down Expand Up @@ -159,7 +159,7 @@ export class MapRepository implements IMapRepository {
`Response from database for natural earth reverse geocoding latitude: ${point.latitude}, longitude: ${point.longitude} was null`,
);

return null;
return { country: null, state: null, city: null };
}

this.logger.verbose(`Raw: ${JSON.stringify(ne_response, ['id', 'admin', 'admin_a3', 'type'], 2)}`);
Expand Down
6 changes: 3 additions & 3 deletions server/src/repositories/metadata.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export class MetadataRepository implements IMetadataRepository {
await this.exiftool.end();
}

readTags(path: string): Promise<ImmichTags | null> {
readTags(path: string): Promise<ImmichTags> {
return this.exiftool.read(path).catch((error) => {
this.logger.warn(`Error reading exif data (${path}): ${error}`, error?.stack);
return null;
}) as Promise<ImmichTags | null>;
return {};
}) as Promise<ImmichTags>;
}

extractBinaryTag(path: string, tagName: string): Promise<Buffer> {
Expand Down
7 changes: 6 additions & 1 deletion server/src/services/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
import { UserEntity } from 'src/entities/user.entity';
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
Expand All @@ -20,6 +21,7 @@ import { systemConfigStub } from 'test/fixtures/system-config.stub';
import { userStub } from 'test/fixtures/user.stub';
import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock';
import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock';
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { newSessionRepositoryMock } from 'test/repositories/session.repository.mock';
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
Expand Down Expand Up @@ -56,6 +58,7 @@ const oauthUserWithDefaultQuota = {
describe('AuthService', () => {
let sut: AuthService;
let cryptoMock: Mocked<ICryptoRepository>;
let eventMock: Mocked<IEventRepository>;
let userMock: Mocked<IUserRepository>;
let loggerMock: Mocked<ILoggerRepository>;
let systemMock: Mocked<ISystemMetadataRepository>;
Expand Down Expand Up @@ -87,14 +90,15 @@ describe('AuthService', () => {
} as any);

cryptoMock = newCryptoRepositoryMock();
eventMock = newEventRepositoryMock();
userMock = newUserRepositoryMock();
loggerMock = newLoggerRepositoryMock();
systemMock = newSystemMetadataRepositoryMock();
sessionMock = newSessionRepositoryMock();
shareMock = newSharedLinkRepositoryMock();
keyMock = newKeyRepositoryMock();

sut = new AuthService(cryptoMock, systemMock, loggerMock, userMock, sessionMock, shareMock, keyMock);
sut = new AuthService(cryptoMock, eventMock, systemMock, loggerMock, userMock, sessionMock, shareMock, keyMock);
});

it('should be defined', () => {
Expand Down Expand Up @@ -208,6 +212,7 @@ describe('AuthService', () => {
});

expect(sessionMock.delete).toHaveBeenCalledWith('token123');
expect(eventMock.emit).toHaveBeenCalledWith('session.delete', { sessionId: 'token123' });
});

it('should return the default redirect if auth type is OAUTH but oauth is not enabled', async () => {
Expand Down
3 changes: 3 additions & 0 deletions server/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { UserEntity } from 'src/entities/user.entity';
import { Permission } from 'src/enum';
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
Expand Down Expand Up @@ -75,6 +76,7 @@ export class AuthService {

constructor(
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
@Inject(IEventRepository) private eventRepository: IEventRepository,
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
@Inject(IUserRepository) private userRepository: IUserRepository,
Expand Down Expand Up @@ -114,6 +116,7 @@ export class AuthService {
async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {
if (auth.session) {
await this.sessionRepository.delete(auth.session.id);
await this.eventRepository.emit('session.delete', { sessionId: auth.session.id });
}

return {
Expand Down
7 changes: 5 additions & 2 deletions server/src/services/metadata.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,13 +522,13 @@ describe(MetadataService.name, () => {
it('should extract the correct video orientation', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.video]);
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
metadataMock.readTags.mockResolvedValue(null);
metadataMock.readTags.mockResolvedValue({});

await sut.handleMetadataExtraction({ id: assetStub.video.id });

expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.video.id]);
expect(assetMock.upsertExif).toHaveBeenCalledWith(
expect.objectContaining({ orientation: Orientation.Rotate270CW }),
expect.objectContaining({ orientation: Orientation.Rotate270CW.toString() }),
);
});

Expand Down Expand Up @@ -814,6 +814,9 @@ describe(MetadataService.name, () => {
projectionType: 'EQUIRECTANGULAR',
timeZone: tags.tz,
rating: tags.Rating,
country: null,
state: null,
city: null,
});
expect(assetMock.update).toHaveBeenCalledWith({
id: assetStub.image.id,
Expand Down
Loading

0 comments on commit 1503ebd

Please sign in to comment.