chore: merge main into feat/hero_view_transitions

Change-Id: I6e6316f66343b8f3ea9fe33ed3f8f3e56a6a6964
This commit is contained in:
midzelis
2026-05-14 02:30:18 +00:00
379 changed files with 25782 additions and 3260 deletions
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
OPENAPI_GENERATOR_VERSION=v7.12.0
set -euo pipefail
# usage: ./bin/generate-dart-sdk.sh
rm -rf ../mobile/openapi
cd ./templates/mobile/serialization/native
wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache
patch --no-backup-if-mismatch -u native_class.mustache <native_class.mustache.patch
patch --no-backup-if-mismatch -u native_class.mustache <native_class_nullable_items_in_arrays.patch
cd ../../
wget -O api.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/api.mustache
patch --no-backup-if-mismatch -u api.mustache <api.mustache.patch
cd ../../
pnpm dlx --allow-build="" @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./templates/mobile
# Post generate patches
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api_client.dart <./patch/api_client.dart.patch
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api.dart <./patch/api.dart.patch
patch --no-backup-if-mismatch -u ../mobile/openapi/pubspec.yaml <./patch/pubspec_immich_mobile.yaml.patch
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/model/asset_edit_action_item_dto.dart <./patch/asset_edit_action_item_dto.dart.patch
# Don't include analysis_options.yaml for the generated openapi files
# so that language servers can properly exclude the mobile/openapi directory
rm ../mobile/openapi/analysis_options.yaml
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env bash
OPENAPI_GENERATOR_VERSION=v7.12.0
set -euo pipefail
# usage: ./bin/generate-open-api.sh
function dart {
rm -rf ../mobile/openapi
cd ./templates/mobile/serialization/native
wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache
patch --no-backup-if-mismatch -u native_class.mustache <native_class.mustache.patch
patch --no-backup-if-mismatch -u native_class.mustache <native_class_nullable_items_in_arrays.patch
cd ../../
wget -O api.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/api.mustache
patch --no-backup-if-mismatch -u api.mustache <api.mustache.patch
cd ../../
pnpm dlx --allow-build="" @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./templates/mobile
# Post generate patches
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api_client.dart <./patch/api_client.dart.patch
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api.dart <./patch/api.dart.patch
patch --no-backup-if-mismatch -u ../mobile/openapi/pubspec.yaml <./patch/pubspec_immich_mobile.yaml.patch
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/model/asset_edit_action_item_dto.dart <./patch/asset_edit_action_item_dto.dart.patch
# Don't include analysis_options.yaml for the generated openapi files
# so that language servers can properly exclude the mobile/openapi directory
rm ../mobile/openapi/analysis_options.yaml
}
function typescript {
pnpm dlx oazapfts --optimistic --argumentStyle=object --useEnumType --allSchemas immich-openapi-specs.json typescript-sdk/src/fetch-client.ts
pnpm --filter @immich/sdk install --frozen-lockfile
pnpm --filter @immich/sdk build
}
# requires server to be built
(
cd ..
SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich build
pnpm --filter immich sync:open-api
)
if [[ $# -ge 1 ]] && [[ $1 == 'dart' ]]; then
dart
elif [[ $# -ge 1 ]] && [[ $1 == 'typescript' ]]; then
typescript
else
dart
typescript
fi
+55 -156
View File
@@ -13322,6 +13322,15 @@
"$ref": "#/components/schemas/AssetOrder"
}
},
{
"name": "orderBy",
"required": false,
"in": "query",
"description": "Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)",
"schema": {
"$ref": "#/components/schemas/AssetOrderBy"
}
},
{
"name": "personId",
"required": false,
@@ -13512,6 +13521,15 @@
"$ref": "#/components/schemas/AssetOrder"
}
},
{
"name": "orderBy",
"required": false,
"in": "query",
"description": "Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)",
"schema": {
"$ref": "#/components/schemas/AssetOrderBy"
}
},
{
"name": "personId",
"required": false,
@@ -16022,6 +16040,7 @@
"type": "object"
},
"AssetFaceResponseDto": {
"description": "Asset face with person",
"properties": {
"boundingBoxX1": {
"description": "Bounding box X1 coordinate",
@@ -16125,66 +16144,6 @@
],
"type": "object"
},
"AssetFaceWithoutPersonResponseDto": {
"description": "Asset face without person",
"properties": {
"boundingBoxX1": {
"description": "Bounding box X1 coordinate",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"boundingBoxX2": {
"description": "Bounding box X2 coordinate",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"boundingBoxY1": {
"description": "Bounding box Y1 coordinate",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"boundingBoxY2": {
"description": "Bounding box Y2 coordinate",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"id": {
"description": "Face ID",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
"type": "string"
},
"imageHeight": {
"description": "Image height in pixels",
"maximum": 9007199254740991,
"minimum": 0,
"type": "integer"
},
"imageWidth": {
"description": "Image width in pixels",
"maximum": 9007199254740991,
"minimum": 0,
"type": "integer"
},
"sourceType": {
"$ref": "#/components/schemas/SourceType"
}
},
"required": [
"boundingBoxX1",
"boundingBoxX2",
"boundingBoxY1",
"boundingBoxY2",
"id",
"imageHeight",
"imageWidth"
],
"type": "object"
},
"AssetIdErrorReason": {
"description": "Error reason if failed",
"enum": [
@@ -16271,7 +16230,7 @@
},
"duration": {
"description": "Duration in milliseconds (for videos)",
"maximum": 2147483647,
"maximum": 9007199254740991,
"minimum": 0,
"type": "integer"
},
@@ -16616,6 +16575,14 @@
],
"type": "string"
},
"AssetOrderBy": {
"description": "Asset sorting property",
"enum": [
"takenAt",
"createdAt"
],
"type": "string"
},
"AssetRejectReason": {
"description": "Rejection reason if rejected",
"enum": [
@@ -16755,7 +16722,7 @@
},
"people": {
"items": {
"$ref": "#/components/schemas/PersonWithFacesResponseDto"
"$ref": "#/components/schemas/PersonResponseDto"
},
"type": "array"
},
@@ -16796,12 +16763,6 @@
"type": {
"$ref": "#/components/schemas/AssetTypeEnum"
},
"unassignedFaces": {
"items": {
"$ref": "#/components/schemas/AssetFaceWithoutPersonResponseDto"
},
"type": "array"
},
"updatedAt": {
"description": "The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified.",
"format": "date-time",
@@ -16929,7 +16890,6 @@
"enum": [
"mp3",
"aac",
"libopus",
"opus",
"pcm_s16le"
],
@@ -19765,93 +19725,6 @@
},
"type": "object"
},
"PersonWithFacesResponseDto": {
"properties": {
"birthDate": {
"description": "Person date of birth",
"format": "date",
"nullable": true,
"type": "string"
},
"color": {
"description": "Person color (hex)",
"type": "string",
"x-immich-history": [
{
"version": "v1.126.0",
"state": "Added"
},
{
"version": "v2",
"state": "Stable"
}
],
"x-immich-state": "Stable"
},
"faces": {
"items": {
"$ref": "#/components/schemas/AssetFaceWithoutPersonResponseDto"
},
"type": "array"
},
"id": {
"description": "Person ID",
"type": "string"
},
"isFavorite": {
"description": "Is favorite",
"type": "boolean",
"x-immich-history": [
{
"version": "v1.126.0",
"state": "Added"
},
{
"version": "v2",
"state": "Stable"
}
],
"x-immich-state": "Stable"
},
"isHidden": {
"description": "Is hidden",
"type": "boolean"
},
"name": {
"description": "Person name",
"type": "string"
},
"thumbnailPath": {
"description": "Thumbnail path",
"type": "string"
},
"updatedAt": {
"description": "Last update date",
"format": "date-time",
"type": "string",
"x-immich-history": [
{
"version": "v1.107.0",
"state": "Added"
},
{
"version": "v2",
"state": "Stable"
}
],
"x-immich-state": "Stable"
}
},
"required": [
"birthDate",
"faces",
"id",
"isHidden",
"name",
"thumbnailPath"
],
"type": "object"
},
"PinCodeChangeDto": {
"properties": {
"newPinCode": {
@@ -23067,6 +22940,14 @@
"description": "Checksum",
"type": "string"
},
"createdAt": {
"description": "Uploaded to Immich at",
"example": "2024-01-01T00:00:00.000Z",
"format": "date-time",
"nullable": true,
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"type": "string"
},
"deletedAt": {
"description": "Deleted at",
"example": "2024-01-01T00:00:00.000Z",
@@ -23167,6 +23048,7 @@
},
"required": [
"checksum",
"createdAt",
"deletedAt",
"duration",
"fileCreatedAt",
@@ -23194,6 +23076,14 @@
"description": "Checksum",
"type": "string"
},
"createdAt": {
"description": "Uploaded to Immich at",
"example": "2024-01-01T00:00:00.000Z",
"format": "date-time",
"nullable": true,
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"type": "string"
},
"deletedAt": {
"description": "Deleted at",
"example": "2024-01-01T00:00:00.000Z",
@@ -23296,6 +23186,7 @@
},
"required": [
"checksum",
"createdAt",
"deletedAt",
"duration",
"fileCreatedAt",
@@ -25144,6 +25035,13 @@
},
"type": "array"
},
"createdAt": {
"description": "Array of UTC timestamps when each asset was originally uploaded to Immich",
"items": {
"type": "string"
},
"type": "array"
},
"duration": {
"description": "Array of video/gif durations in milliseconds (null for static images)",
"items": {
@@ -25274,6 +25172,7 @@
"required": [
"city",
"country",
"createdAt",
"duration",
"fileCreatedAt",
"id",
-3
View File
@@ -1,3 +0,0 @@
package-lock.json
tsconfig.json
src/
-26
View File
@@ -1,26 +0,0 @@
# @immich/sdk
A TypeScript SDK for interfacing with the [Immich](https://immich.app/) API.
## Install
```bash
npm i --save @immich/sdk
```
## Usage
For a more detailed example, check out the [`@immich/cli`](https://github.com/immich-app/immich/tree/main/cli).
```typescript
import { getAllAlbums, getMyUser, init } from "@immich/sdk";
const API_KEY = "<API_KEY>"; // process.env.IMMICH_API_KEY
init({ baseUrl: "https://demo.immich.app/api", apiKey: API_KEY });
const user = await getMyUser();
const albums = await getAllAlbums({});
console.log({ user, albums });
```
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
import { HttpError } from '@oazapfts/runtime';
export interface ApiValidationError {
code: string;
path: (string | number)[];
message: string;
}
export interface ApiExceptionResponse {
message: string;
error?: string;
statusCode: number;
errors?: ApiValidationError[];
}
export interface ApiHttpError extends HttpError {
data: ApiExceptionResponse;
}
export declare function isHttpError(error: unknown): error is ApiHttpError;
@@ -0,0 +1,4 @@
import { HttpError } from '@oazapfts/runtime';
export function isHttpError(error) {
return error instanceof HttpError;
}
+18
View File
@@ -0,0 +1,18 @@
export * from './fetch-client.js';
export * from './fetch-errors.js';
export interface InitOptions {
baseUrl: string;
apiKey: string;
headers?: Record<string, string>;
}
export declare const init: ({ baseUrl, apiKey, headers }: InitOptions) => void;
export declare const getBaseUrl: () => string;
export declare const setBaseUrl: (baseUrl: string) => void;
export declare const setApiKey: (apiKey: string) => void;
export declare const setHeader: (key: string, value: string) => void;
export declare const setHeaders: (headers: Record<string, string>) => void;
export declare const getAssetOriginalPath: (id: string) => string;
export declare const getAssetThumbnailPath: (id: string) => string;
export declare const getAssetPlaybackPath: (id: string) => string;
export declare const getUserProfileImagePath: (userId: string) => string;
export declare const getPeopleThumbnailPath: (personId: string) => string;
+40
View File
@@ -0,0 +1,40 @@
import { defaults } from './fetch-client.js';
export * from './fetch-client.js';
export * from './fetch-errors.js';
export const init = ({ baseUrl, apiKey, headers }) => {
setBaseUrl(baseUrl);
setApiKey(apiKey);
if (headers) {
setHeaders(headers);
}
};
export const getBaseUrl = () => defaults.baseUrl;
export const setBaseUrl = (baseUrl) => {
defaults.baseUrl = baseUrl;
};
export const setApiKey = (apiKey) => {
defaults.headers = defaults.headers || {};
defaults.headers['x-api-key'] = apiKey;
};
export const setHeader = (key, value) => {
assertNoApiKey(key);
defaults.headers = defaults.headers || {};
defaults.headers[key] = value;
};
export const setHeaders = (headers) => {
defaults.headers = defaults.headers || {};
for (const [key, value] of Object.entries(headers)) {
assertNoApiKey(key);
defaults.headers[key] = value;
}
};
const assertNoApiKey = (headerKey) => {
if (headerKey.toLowerCase() === 'x-api-key') {
throw new Error('The API key header can only be set using setApiKey().');
}
};
export const getAssetOriginalPath = (id) => `/assets/${id}/original`;
export const getAssetThumbnailPath = (id) => `/assets/${id}/thumbnail`;
export const getAssetPlaybackPath = (id) => `/assets/${id}/video/playback`;
export const getUserProfileImagePath = (userId) => `/users/${userId}/profile-image`;
export const getPeopleThumbnailPath = (personId) => `/people/${personId}/thumbnail`;
-30
View File
@@ -1,30 +0,0 @@
{
"name": "@immich/sdk",
"version": "2.7.5",
"description": "Auto-generated TypeScript SDK for the Immich API",
"repository": {
"type": "git",
"url": "git+https://github.com/immich-app/immich.git",
"directory": "open-api/typescript-sdk"
},
"type": "module",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"default": "./build/index.js"
}
},
"scripts": {
"build": "tsc"
},
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^24.12.2",
"typescript": "^6.0.0"
}
}
@@ -1,22 +0,0 @@
import { HttpError } from '@oazapfts/runtime';
export interface ApiValidationError {
code: string;
path: (string | number)[];
message: string;
}
export interface ApiExceptionResponse {
message: string;
error?: string;
statusCode: number;
errors?: ApiValidationError[];
}
export interface ApiHttpError extends HttpError {
data: ApiExceptionResponse;
}
export function isHttpError(error: unknown): error is ApiHttpError {
return error instanceof HttpError;
}
-62
View File
@@ -1,62 +0,0 @@
import { defaults } from './fetch-client.js';
export * from './fetch-client.js';
export * from './fetch-errors.js';
export interface InitOptions {
baseUrl: string;
apiKey: string;
headers?: Record<string, string>;
}
export const init = ({ baseUrl, apiKey, headers }: InitOptions) => {
setBaseUrl(baseUrl);
setApiKey(apiKey);
if (headers) {
setHeaders(headers);
}
};
export const getBaseUrl = () => defaults.baseUrl;
export const setBaseUrl = (baseUrl: string) => {
defaults.baseUrl = baseUrl;
};
export const setApiKey = (apiKey: string) => {
defaults.headers = defaults.headers || {};
defaults.headers['x-api-key'] = apiKey;
};
export const setHeader = (key: string, value: string) => {
assertNoApiKey(key);
defaults.headers = defaults.headers || {};
defaults.headers[key] = value;
};
export const setHeaders = (headers: Record<string, string>) => {
defaults.headers = defaults.headers || {};
for (const [key, value] of Object.entries(headers)) {
assertNoApiKey(key);
defaults.headers[key] = value;
}
};
const assertNoApiKey = (headerKey: string) => {
if (headerKey.toLowerCase() === 'x-api-key') {
throw new Error('The API key header can only be set using setApiKey().');
}
};
export const getAssetOriginalPath = (id: string) => `/assets/${id}/original`;
export const getAssetThumbnailPath = (id: string) => `/assets/${id}/thumbnail`;
export const getAssetPlaybackPath = (id: string) =>
`/assets/${id}/video/playback`;
export const getUserProfileImagePath = (userId: string) =>
`/users/${userId}/profile-image`;
export const getPeopleThumbnailPath = (personId: string) =>
`/people/${personId}/thumbnail`;
-14
View File
@@ -1,14 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"strict": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "build",
"module": "Node16",
"moduleResolution": "Node16",
"rootDir": "./src",
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts"]
}