mirror of
https://github.com/immich-app/immich.git
synced 2026-02-01 01:34:49 -08:00
Compare commits
1 Commits
feat/plugi
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6b0f0c76a |
@@ -34,7 +34,7 @@
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^60.0.0",
|
||||
"exiftool-vendored": "^31.1.0",
|
||||
"exiftool-vendored": "^28.3.1",
|
||||
"globals": "^16.0.0",
|
||||
"jose": "^5.6.3",
|
||||
"luxon": "^3.4.4",
|
||||
|
||||
@@ -1604,6 +1604,7 @@
|
||||
"read_changelog": "Read Changelog",
|
||||
"readonly_mode_disabled": "Read-only mode disabled",
|
||||
"readonly_mode_enabled": "Read-only mode enabled",
|
||||
"plugins": "Plugins",
|
||||
"ready_for_upload": "Ready for upload",
|
||||
"reassign": "Reassign",
|
||||
"reassigned_assets_to_existing_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to {name, select, null {an existing person} other {{name}}}",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[tools]
|
||||
node = "22.20.0"
|
||||
flutter = "3.35.6"
|
||||
pnpm = "10.18.1"
|
||||
flutter = "3.35.5"
|
||||
pnpm = "10.18.3"
|
||||
terragrunt = "0.58.12"
|
||||
opentofu = "1.7.1"
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"flutter": "3.35.6"
|
||||
"flutter": "3.35.4"
|
||||
}
|
||||
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.35.6",
|
||||
"dart.flutterSdkPath": ".fvm/versions/3.35.4",
|
||||
"dart.lineLength": 120,
|
||||
"[dart]": {
|
||||
"editor.rulers": [120]
|
||||
|
||||
@@ -481,6 +481,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
easy_image_viewer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_image_viewer
|
||||
sha256: fb6cb123c3605552cc91150dcdb50ca977001dcddfb71d20caa0c5edc9a80947
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
easy_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1405,6 +1413,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.6.4"
|
||||
photo_manager_image_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: photo_manager_image_provider
|
||||
sha256: b6015b67b32f345f57cf32c126f871bced2501236c405aafaefa885f7c821e4f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pigeon:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -2164,4 +2180,4 @@ packages:
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
flutter: ">=3.35.6"
|
||||
flutter: ">=3.35.4"
|
||||
|
||||
@@ -6,9 +6,12 @@ version: 2.1.0+3022
|
||||
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
flutter: 3.35.6
|
||||
flutter: 3.35.4
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
async: ^2.11.0
|
||||
auto_route: ^9.2.0
|
||||
background_downloader: ^9.2.5
|
||||
@@ -20,15 +23,10 @@ dependencies:
|
||||
crop_image: ^1.0.16
|
||||
crypto: ^3.0.6
|
||||
device_info_plus: ^12.0.0
|
||||
# DB
|
||||
drift: ^2.23.1
|
||||
drift_flutter: ^0.2.4
|
||||
dynamic_color: ^1.7.0
|
||||
easy_image_viewer: ^1.5.1
|
||||
easy_localization: ^3.0.7+1
|
||||
ffi: ^2.1.4
|
||||
file_picker: ^8.0.0+1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_cache_manager: ^3.4.1
|
||||
flutter_displaymode: ^0.6.0
|
||||
flutter_hooks: ^0.21.2
|
||||
@@ -39,39 +37,26 @@ dependencies:
|
||||
flutter_web_auth_2: ^5.0.0-alpha.0
|
||||
fluttertoast: ^8.2.12
|
||||
geolocator: ^14.0.0
|
||||
home_widget: ^0.8.0
|
||||
hooks_riverpod: ^2.6.1
|
||||
home_widget: ^0.8.0
|
||||
http: ^1.3.0
|
||||
image_picker: ^1.1.2
|
||||
intl: ^0.20.0
|
||||
isar:
|
||||
git:
|
||||
url: https://github.com/immich-app/isar
|
||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||
path: packages/isar/
|
||||
isar_community_flutter_libs: 3.3.0-dev.3
|
||||
local_auth: ^2.3.0
|
||||
logging: ^1.3.0
|
||||
maplibre_gl: ^0.22.0
|
||||
|
||||
native_video_player:
|
||||
git:
|
||||
url: https://github.com/immich-app/native_video_player
|
||||
ref: '893894b'
|
||||
network_info_plus: ^6.1.3
|
||||
octo_image: ^2.1.0
|
||||
openapi:
|
||||
path: openapi
|
||||
package_info_plus: ^8.3.0
|
||||
path: ^1.9.1
|
||||
path_provider: ^2.1.5
|
||||
path_provider_foundation: ^2.4.1
|
||||
permission_handler: ^11.4.0
|
||||
photo_manager: ^3.6.4
|
||||
photo_manager_image_provider: ^2.2.0
|
||||
pinput: ^5.0.1
|
||||
punycode: ^1.0.0
|
||||
riverpod_annotation: ^2.6.1
|
||||
scroll_date_picker: ^3.8.0
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
share_handler: ^0.0.22
|
||||
share_plus: ^10.1.4
|
||||
@@ -84,34 +69,52 @@ dependencies:
|
||||
uuid: ^4.5.1
|
||||
wakelock_plus: ^1.2.10
|
||||
worker_manager: ^7.2.3
|
||||
scroll_date_picker: ^3.8.0
|
||||
ffi: ^2.1.4
|
||||
|
||||
native_video_player:
|
||||
git:
|
||||
url: https://github.com/immich-app/native_video_player
|
||||
ref: '893894b'
|
||||
openapi:
|
||||
path: openapi
|
||||
isar:
|
||||
git:
|
||||
url: https://github.com/immich-app/isar
|
||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||
path: packages/isar/
|
||||
isar_community_flutter_libs: 3.3.0-dev.3
|
||||
# DB
|
||||
drift: ^2.23.1
|
||||
drift_flutter: ^0.2.4
|
||||
|
||||
dev_dependencies:
|
||||
auto_route_generator: ^9.0.0
|
||||
build_runner: ^2.4.8
|
||||
custom_lint: ^0.7.5
|
||||
# Drift generator
|
||||
drift_dev: ^2.23.1
|
||||
fake_async: ^1.3.1
|
||||
file: ^7.0.1 # for MemoryFileSystem
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
flutter_lints: ^5.0.0
|
||||
flutter_native_splash: ^2.4.5
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
immich_mobile_immich_lint:
|
||||
path: './immich_lint'
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
build_runner: ^2.4.8
|
||||
auto_route_generator: ^9.0.0
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
flutter_native_splash: ^2.4.5
|
||||
isar_generator:
|
||||
git:
|
||||
url: https://github.com/immich-app/isar
|
||||
ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a'
|
||||
path: packages/isar_generator/
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
custom_lint: ^0.7.5
|
||||
riverpod_lint: ^2.6.1
|
||||
riverpod_generator: ^2.6.1
|
||||
mocktail: ^1.0.4
|
||||
immich_mobile_immich_lint:
|
||||
path: './immich_lint'
|
||||
fake_async: ^1.3.1
|
||||
file: ^7.0.1 # for MemoryFileSystem
|
||||
# Drift generator
|
||||
drift_dev: ^2.23.1
|
||||
# Type safe platform code
|
||||
pigeon: ^26.0.0
|
||||
riverpod_generator: ^2.6.1
|
||||
riverpod_lint: ^2.6.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
@@ -5717,6 +5717,154 @@
|
||||
"description": "This endpoint requires the `person.read` permission."
|
||||
}
|
||||
},
|
||||
"/plugins": {
|
||||
"get": {
|
||||
"operationId": "searchPlugins",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "isEnabled",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isInstalled",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "isTrusted",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PluginResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Plugin"
|
||||
],
|
||||
"x-immich-admin-only": true,
|
||||
"x-immich-permission": "plugin.read",
|
||||
"description": "This endpoint is an admin-only route, and requires the `plugin.read` permission."
|
||||
}
|
||||
},
|
||||
"/plugins/{id}": {
|
||||
"delete": {
|
||||
"operationId": "deletePlugin",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Plugin"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updatePlugin",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PluginUpdateDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PluginResponseDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Plugin"
|
||||
],
|
||||
"x-immich-admin-only": true,
|
||||
"x-immich-permission": "plugin.update",
|
||||
"description": "This endpoint is an admin-only route, and requires the `plugin.update` permission."
|
||||
}
|
||||
},
|
||||
"/search/cities": {
|
||||
"get": {
|
||||
"operationId": "getAssetsByCity",
|
||||
@@ -13211,6 +13359,9 @@
|
||||
"person.statistics",
|
||||
"person.merge",
|
||||
"person.reassign",
|
||||
"plugin.read",
|
||||
"plugin.update",
|
||||
"plugin.delete",
|
||||
"pinCode.create",
|
||||
"pinCode.update",
|
||||
"pinCode.delete",
|
||||
@@ -13498,6 +13649,66 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginResponseDto": {
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"isEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isInstalled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isTrusted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"packageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"createdAt",
|
||||
"description",
|
||||
"id",
|
||||
"isEnabled",
|
||||
"isInstalled",
|
||||
"isTrusted",
|
||||
"name",
|
||||
"packageId",
|
||||
"updatedAt",
|
||||
"version"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PluginUpdateDto": {
|
||||
"properties": {
|
||||
"isEnabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"isEnabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"PurchaseResponse": {
|
||||
"properties": {
|
||||
"hideBuyButtonUntil": {
|
||||
|
||||
2
plugins/.gitignore
vendored
2
plugins/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -1,26 +0,0 @@
|
||||
Copyright 2024, The Extism Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,12 +0,0 @@
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
esbuild
|
||||
.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
outdir: 'dist',
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
minify: false, // might want to use true for production build
|
||||
format: 'cjs', // needs to be CJS for now
|
||||
target: ['es2020'] // don't go over es2020 because quickjs doesn't support it
|
||||
})
|
||||
443
plugins/package-lock.json
generated
443
plugins/package-lock.json
generated
@@ -1,443 +0,0 @@
|
||||
{
|
||||
"name": "js-pdk-template",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "js-pdk-template",
|
||||
"version": "1.0.0",
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@extism/js-pdk": "^1.0.1",
|
||||
"esbuild": "^0.19.6",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
|
||||
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
|
||||
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
|
||||
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
|
||||
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
|
||||
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
|
||||
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
|
||||
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
|
||||
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
|
||||
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
|
||||
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@extism/js-pdk": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@extism/js-pdk/-/js-pdk-1.0.1.tgz",
|
||||
"integrity": "sha512-YJWfHGeOuJnQw4V8NPNHvbSr6S8iDd2Ga6VEukwlRP7tu62ozTxIgokYw8i+rajD/16zz/gK0KYARBpm2qPAmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
||||
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.19.12",
|
||||
"@esbuild/android-arm": "0.19.12",
|
||||
"@esbuild/android-arm64": "0.19.12",
|
||||
"@esbuild/android-x64": "0.19.12",
|
||||
"@esbuild/darwin-arm64": "0.19.12",
|
||||
"@esbuild/darwin-x64": "0.19.12",
|
||||
"@esbuild/freebsd-arm64": "0.19.12",
|
||||
"@esbuild/freebsd-x64": "0.19.12",
|
||||
"@esbuild/linux-arm": "0.19.12",
|
||||
"@esbuild/linux-arm64": "0.19.12",
|
||||
"@esbuild/linux-ia32": "0.19.12",
|
||||
"@esbuild/linux-loong64": "0.19.12",
|
||||
"@esbuild/linux-mips64el": "0.19.12",
|
||||
"@esbuild/linux-ppc64": "0.19.12",
|
||||
"@esbuild/linux-riscv64": "0.19.12",
|
||||
"@esbuild/linux-s390x": "0.19.12",
|
||||
"@esbuild/linux-x64": "0.19.12",
|
||||
"@esbuild/netbsd-x64": "0.19.12",
|
||||
"@esbuild/openbsd-x64": "0.19.12",
|
||||
"@esbuild/sunos-x64": "0.19.12",
|
||||
"@esbuild/win32-arm64": "0.19.12",
|
||||
"@esbuild/win32-ia32": "0.19.12",
|
||||
"@esbuild/win32-x64": "0.19.12"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
|
||||
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "plugins",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm build:tsc && pnpm build:wasm",
|
||||
"build:tsc": "tsc --noEmit && node esbuild.js",
|
||||
"build:wasm": "extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "BSD-3-Clause",
|
||||
"devDependencies": {
|
||||
"@extism/js-pdk": "^1.0.1",
|
||||
"esbuild": "^0.19.6",
|
||||
"typescript": "^5.3.2"
|
||||
}
|
||||
}
|
||||
9
plugins/src/index.d.ts
vendored
9
plugins/src/index.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
declare module 'main' {
|
||||
export function archiveAssetAction(): I32;
|
||||
}
|
||||
|
||||
declare module 'extism:host' {
|
||||
interface user {
|
||||
updateAsset(ptr: PTR): I32;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const { updateAsset } = Host.getFunctions();
|
||||
|
||||
export function archiveAssetAction() {
|
||||
const event = JSON.parse(Host.inputString());
|
||||
const ptr = Memory.fromString(
|
||||
JSON.stringify({
|
||||
id: event.asset.id,
|
||||
visibility: 'archive',
|
||||
})
|
||||
);
|
||||
|
||||
updateAsset(ptr.offset);
|
||||
|
||||
ptr.free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020", // Specify ECMAScript target version
|
||||
"module": "commonjs", // Specify module code generation
|
||||
"lib": [
|
||||
"es2020"
|
||||
], // Specify a list of library files to be included in the compilation
|
||||
"types": [
|
||||
"@extism/js-pdk",
|
||||
"./src/index.d.ts"
|
||||
], // Specify a list of type definition files to be included in the compilation
|
||||
"strict": true, // Enable all strict type-checking options
|
||||
"esModuleInterop": true, // Enables compatibility with Babel-style module imports
|
||||
"skipLibCheck": true, // Skip type checking of declaration files
|
||||
"allowJs": true, // Allow JavaScript files to be compiled
|
||||
"noEmit": true // Do not emit outputs (no .js or .d.ts files)
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts" // Include all TypeScript files in src directory
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules" // Exclude the node_modules directory
|
||||
]
|
||||
}
|
||||
82
pnpm-lock.yaml
generated
82
pnpm-lock.yaml
generated
@@ -238,8 +238,8 @@ importers:
|
||||
specifier: ^60.0.0
|
||||
version: 60.0.0(eslint@9.37.0(jiti@2.6.1))
|
||||
exiftool-vendored:
|
||||
specifier: ^31.1.0
|
||||
version: 31.1.0
|
||||
specifier: ^28.3.1
|
||||
version: 28.8.0
|
||||
globals:
|
||||
specifier: ^16.0.0
|
||||
version: 16.4.0
|
||||
@@ -299,23 +299,8 @@ importers:
|
||||
specifier: ^5.3.3
|
||||
version: 5.9.3
|
||||
|
||||
plugins:
|
||||
devDependencies:
|
||||
'@extism/js-pdk':
|
||||
specifier: ^1.0.1
|
||||
version: 1.1.1
|
||||
esbuild:
|
||||
specifier: ^0.19.6
|
||||
version: 0.19.12
|
||||
typescript:
|
||||
specifier: ^5.3.2
|
||||
version: 5.9.3
|
||||
|
||||
server:
|
||||
dependencies:
|
||||
'@extism/extism':
|
||||
specifier: 2.0.0-rc13
|
||||
version: 2.0.0-rc13
|
||||
'@nestjs/bullmq':
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.4(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(bullmq@5.61.0)
|
||||
@@ -419,8 +404,8 @@ importers:
|
||||
specifier: 4.3.3
|
||||
version: 4.3.3
|
||||
exiftool-vendored:
|
||||
specifier: ^31.1.0
|
||||
version: 31.1.0
|
||||
specifier: ^28.8.0
|
||||
version: 28.8.0
|
||||
express:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
@@ -2540,12 +2525,6 @@ packages:
|
||||
resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@extism/extism@2.0.0-rc13':
|
||||
resolution: {integrity: sha512-iQ3mrPKOC0WMZ94fuJrKbJmMyz4LQ9Abf8gd4F5ShxKWa+cRKcVzk0EqRQsp5xXsQ2dO3zJTiA6eTc4Ihf7k+A==}
|
||||
|
||||
'@extism/js-pdk@1.1.1':
|
||||
resolution: {integrity: sha512-VZLn/dX0ttA1uKk2PZeR/FL3N+nA1S5Vc7E5gdjkR60LuUIwCZT9cYON245V4HowHlBA7YOegh0TLjkx+wNbrA==}
|
||||
|
||||
'@faker-js/faker@10.1.0':
|
||||
resolution: {integrity: sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'}
|
||||
@@ -3516,8 +3495,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@photo-sphere-viewer/core': 5.14.0
|
||||
|
||||
'@photostructure/tz-lookup@11.2.1':
|
||||
resolution: {integrity: sha512-ugPtvpdLwGQ8IWezSGFgUCYOpO/XXetfKLNv+UN2jjTYyfIDq9dA21GydGyzXuoQ06nN3VGBd3JxmEu+ZtXScg==}
|
||||
'@photostructure/tz-lookup@11.2.0':
|
||||
resolution: {integrity: sha512-DwrvodcXHNSdGdeSF7SBL5o8aBlsaeuCuG7633F04nYsL3hn5Hxe3z/5kCqxv61J1q7ggKZ27GPylR3x0cPNXQ==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
@@ -5231,9 +5210,9 @@ packages:
|
||||
resolution: {integrity: sha512-qsJ8/X+UypqxHXN75M7dF88jNK37dLBRW7LeUzCPz+TNs37G8cfWy9nWzS+LS//g600zrt2le9KuXt0rWfDz5Q==}
|
||||
hasBin: true
|
||||
|
||||
batch-cluster@15.0.1:
|
||||
resolution: {integrity: sha512-eUmh0ld1AUPKTEmdzwGF9QTSexXAyt9rA1F5zDfW1wUi3okA3Tal4NLdCeFI6aiKpBenQhR6NmK9bW9tBHTGPQ==}
|
||||
engines: {node: '>=20'}
|
||||
batch-cluster@13.0.0:
|
||||
resolution: {integrity: sha512-EreW0Vi8TwovhYUHBXXRA5tthuU2ynGsZFlboyMJHCCUXYa2AjgwnE3ubBOJs2xJLcuXFJbi6c/8pH5+FVj8Og==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
batch@0.6.1:
|
||||
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
|
||||
@@ -6568,18 +6547,16 @@ packages:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
exiftool-vendored.exe@13.38.0:
|
||||
resolution: {integrity: sha512-oZx5enTAvSiIAXL+OEk7nNWrfUhEdKUpaGwDjCmz4VKwOa4HbisqyM808xPGPYj8X7XikcME/fq5hvevPeE3cw==}
|
||||
exiftool-vendored.exe@13.0.0:
|
||||
resolution: {integrity: sha512-4zAMuFGgxZkOoyQIzZMHv1HlvgyJK3AkNqjAgm8A8V0UmOZO7yv3pH49cDV1OduzFJqgs6yQ6eG4OGydhKtxlg==}
|
||||
os: [win32]
|
||||
|
||||
exiftool-vendored.pl@13.38.0:
|
||||
resolution: {integrity: sha512-Q3xl1nnwswrsR5344z4NyqvI74fKwla+VJHY1N+32gcDgt8cs9KBsDUwcNzKHSOSa/MjEfniuCJVrQiqR05iag==}
|
||||
exiftool-vendored.pl@13.0.1:
|
||||
resolution: {integrity: sha512-+BRRzjselpWudKR0ltAW5SUt9T82D+gzQN8DdOQUgnSVWWp7oLCeTGBRptbQz+436Ihn/mPzmo/xnf0cv/Qw1A==}
|
||||
os: ['!win32']
|
||||
hasBin: true
|
||||
|
||||
exiftool-vendored@31.1.0:
|
||||
resolution: {integrity: sha512-q8StxLawHLDvhqv/uoBYCfVbDskn49Cr5ouNCZhh4lgryGu1aymHwK9AvO6RcW2SbPm5MSnQDJOfGp2MW5Nnrw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
exiftool-vendored@28.8.0:
|
||||
resolution: {integrity: sha512-R7tirJLr9fWuH9JS/KFFLB+O7jNGKuPXGxREc6YybYangEudGb+X8ERsYXk9AifMiAWh/2agNfbgkbcQcF+MxA==}
|
||||
|
||||
expect-type@1.2.1:
|
||||
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
|
||||
@@ -10969,9 +10946,6 @@ packages:
|
||||
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
urlpattern-polyfill@8.0.2:
|
||||
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
|
||||
|
||||
utf8-byte-length@1.0.5:
|
||||
resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==}
|
||||
|
||||
@@ -14032,12 +14006,6 @@ snapshots:
|
||||
'@eslint/core': 0.16.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@extism/extism@2.0.0-rc13': {}
|
||||
|
||||
'@extism/js-pdk@1.1.1':
|
||||
dependencies:
|
||||
urlpattern-polyfill: 8.0.2
|
||||
|
||||
'@faker-js/faker@10.1.0': {}
|
||||
|
||||
'@fig/complete-commander@3.2.0(commander@11.1.0)':
|
||||
@@ -15160,7 +15128,7 @@ snapshots:
|
||||
'@photo-sphere-viewer/core': 5.14.0
|
||||
three: 0.180.0
|
||||
|
||||
'@photostructure/tz-lookup@11.2.1': {}
|
||||
'@photostructure/tz-lookup@11.2.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
@@ -17101,7 +17069,7 @@ snapshots:
|
||||
|
||||
baseline-browser-mapping@2.8.15: {}
|
||||
|
||||
batch-cluster@15.0.1: {}
|
||||
batch-cluster@13.0.0: {}
|
||||
|
||||
batch@0.6.1: {}
|
||||
|
||||
@@ -18640,21 +18608,21 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
exiftool-vendored.exe@13.38.0:
|
||||
exiftool-vendored.exe@13.0.0:
|
||||
optional: true
|
||||
|
||||
exiftool-vendored.pl@13.38.0: {}
|
||||
exiftool-vendored.pl@13.0.1: {}
|
||||
|
||||
exiftool-vendored@31.1.0:
|
||||
exiftool-vendored@28.8.0:
|
||||
dependencies:
|
||||
'@photostructure/tz-lookup': 11.2.1
|
||||
'@photostructure/tz-lookup': 11.2.0
|
||||
'@types/luxon': 3.7.1
|
||||
batch-cluster: 15.0.1
|
||||
exiftool-vendored.pl: 13.38.0
|
||||
batch-cluster: 13.0.0
|
||||
exiftool-vendored.pl: 13.0.1
|
||||
he: 1.2.0
|
||||
luxon: 3.7.2
|
||||
optionalDependencies:
|
||||
exiftool-vendored.exe: 13.38.0
|
||||
exiftool-vendored.exe: 13.0.0
|
||||
|
||||
expect-type@1.2.1: {}
|
||||
|
||||
@@ -23955,8 +23923,6 @@ snapshots:
|
||||
punycode: 1.4.1
|
||||
qs: 6.14.0
|
||||
|
||||
urlpattern-polyfill@8.0.2: {}
|
||||
|
||||
utf8-byte-length@1.0.5: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
@@ -4,7 +4,6 @@ packages:
|
||||
- e2e
|
||||
- open-api/typescript-sdk
|
||||
- server
|
||||
- plugins
|
||||
- web
|
||||
- .github
|
||||
ignoredBuiltDependencies:
|
||||
|
||||
@@ -61,7 +61,7 @@ RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
|
||||
# Flutter SDK
|
||||
# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
|
||||
ENV FLUTTER_CHANNEL="stable"
|
||||
ENV FLUTTER_VERSION="3.35.6"
|
||||
ENV FLUTTER_VERSION="3.35.4"
|
||||
ENV FLUTTER_HOME=/flutter
|
||||
ENV PATH=${PATH}:${FLUTTER_HOME}/bin
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"email:dev": "email dev -p 3050 --dir src/emails"
|
||||
},
|
||||
"dependencies": {
|
||||
"@extism/extism": "2.0.0-rc13",
|
||||
"@nestjs/bullmq": "^11.0.1",
|
||||
"@nestjs/common": "^11.0.4",
|
||||
"@nestjs/core": "^11.0.4",
|
||||
@@ -69,7 +68,7 @@
|
||||
"cookie": "^1.0.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cron": "4.3.3",
|
||||
"exiftool-vendored": "^31.1.0",
|
||||
"exiftool-vendored": "^28.8.0",
|
||||
"express": "^5.1.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
|
||||
@@ -18,6 +18,7 @@ import { NotificationController } from 'src/controllers/notification.controller'
|
||||
import { OAuthController } from 'src/controllers/oauth.controller';
|
||||
import { PartnerController } from 'src/controllers/partner.controller';
|
||||
import { PersonController } from 'src/controllers/person.controller';
|
||||
import { PluginController } from 'src/controllers/plugin.controller';
|
||||
import { SearchController } from 'src/controllers/search.controller';
|
||||
import { ServerController } from 'src/controllers/server.controller';
|
||||
import { SessionController } from 'src/controllers/session.controller';
|
||||
@@ -54,6 +55,7 @@ export const controllers = [
|
||||
OAuthController,
|
||||
PartnerController,
|
||||
PersonController,
|
||||
PluginController,
|
||||
SearchController,
|
||||
ServerController,
|
||||
SessionController,
|
||||
|
||||
36
server/src/controllers/plugin.controller.ts
Normal file
36
server/src/controllers/plugin.controller.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Put, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { PluginResponseDto, PluginSearchDto, PluginUpdateDto } from 'src/dtos/plugin.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { PluginService } from 'src/services/plugin.service';
|
||||
import { UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Plugin')
|
||||
@Controller('plugins')
|
||||
export class PluginController {
|
||||
constructor(private service: PluginService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ admin: true, permission: Permission.PluginRead })
|
||||
searchPlugins(@Auth() auth: AuthDto, @Query() dto: PluginSearchDto): Promise<PluginResponseDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ admin: true, permission: Permission.PluginUpdate })
|
||||
updatePlugin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@Body() dto: PluginUpdateDto,
|
||||
): Promise<PluginResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deletePlugin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,19 @@ export type UserAdmin = User & {
|
||||
metadata: UserMetadataItem[];
|
||||
};
|
||||
|
||||
export type Plugin = {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
packageId: string;
|
||||
version: number;
|
||||
name: string;
|
||||
description: string;
|
||||
isEnabled: boolean;
|
||||
isInstalled: boolean;
|
||||
isTrusted: boolean;
|
||||
};
|
||||
|
||||
export type StorageAsset = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
|
||||
58
server/src/dtos/plugin.dto.ts
Normal file
58
server/src/dtos/plugin.dto.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsString } from 'class-validator';
|
||||
import { Plugin } from 'src/database';
|
||||
import { Optional, ValidateBoolean } from 'src/validation';
|
||||
|
||||
export class PluginSearchDto {
|
||||
@ValidateBoolean({ optional: true })
|
||||
isEnabled?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isTrusted?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isInstalled?: boolean;
|
||||
|
||||
@IsString()
|
||||
@Optional()
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export class PluginImportDto {
|
||||
url!: string;
|
||||
install!: boolean;
|
||||
isEnabled!: boolean;
|
||||
isTrusted!: boolean;
|
||||
}
|
||||
|
||||
export class PluginUpdateDto {
|
||||
@IsBoolean()
|
||||
isEnabled!: boolean;
|
||||
}
|
||||
|
||||
export class PluginResponseDto {
|
||||
id!: string;
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
packageId!: string;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
version!: number;
|
||||
name!: string;
|
||||
description!: string;
|
||||
isEnabled!: boolean;
|
||||
isInstalled!: boolean;
|
||||
isTrusted!: boolean;
|
||||
}
|
||||
|
||||
export const mapPlugin = (plugin: Plugin): PluginResponseDto => ({
|
||||
id: plugin.id,
|
||||
createdAt: plugin.createdAt,
|
||||
updatedAt: plugin.updatedAt,
|
||||
packageId: plugin.packageId,
|
||||
version: plugin.version,
|
||||
name: plugin.name,
|
||||
description: plugin.description,
|
||||
isEnabled: plugin.isEnabled,
|
||||
isInstalled: plugin.isInstalled,
|
||||
isTrusted: plugin.isTrusted,
|
||||
});
|
||||
@@ -164,6 +164,10 @@ export enum Permission {
|
||||
PersonMerge = 'person.merge',
|
||||
PersonReassign = 'person.reassign',
|
||||
|
||||
PluginRead = 'plugin.read',
|
||||
PluginUpdate = 'plugin.update',
|
||||
PluginDelete = 'plugin.delete',
|
||||
|
||||
PinCodeCreate = 'pinCode.create',
|
||||
PinCodeUpdate = 'pinCode.update',
|
||||
PinCodeDelete = 'pinCode.delete',
|
||||
|
||||
91
server/src/interfaces/plugin.interface.ts
Normal file
91
server/src/interfaces/plugin.interface.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
export type PluginFactory = {
|
||||
register: () => MaybePromise<Plugin>;
|
||||
};
|
||||
|
||||
export type PluginLike = Plugin | PluginFactory | { default: Plugin } | { plugin: Plugin };
|
||||
|
||||
export interface Plugin<T extends PluginConfig | undefined = undefined> {
|
||||
version: 1;
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
actions: PluginAction<T>[];
|
||||
}
|
||||
|
||||
export type PluginAction<T extends PluginConfig | undefined = undefined> = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
events?: EventType[];
|
||||
config?: T;
|
||||
} & (
|
||||
| { type: ActionType.ASSET; onAction: OnAction<T, AssetDto> }
|
||||
| { type: ActionType.ALBUM; onAction: OnAction<T, AlbumDto> }
|
||||
| { type: ActionType.ALBUM_ASSET; onAction: OnAction<T, { asset: AssetDto; album: AlbumDto }> }
|
||||
);
|
||||
|
||||
export type OnAction<T extends PluginConfig | undefined, D = PluginActionData> = T extends undefined
|
||||
? (ctx: PluginContext, data: D) => MaybePromise<void>
|
||||
: (ctx: PluginContext, data: D, config: InferConfig<T>) => MaybePromise<void>;
|
||||
|
||||
export interface PluginContext {
|
||||
updateAsset: (asset: { id: string; isArchived: boolean }) => Promise<void>;
|
||||
}
|
||||
|
||||
export type PluginActionData = { data: { asset?: AssetDto; album?: AlbumDto } } & (
|
||||
| { type: EventType.ASSET_UPLOAD; data: { asset: AssetDto } }
|
||||
| { type: EventType.ASSET_UPDATE; data: { asset: AssetDto } }
|
||||
| { type: EventType.ASSET_TRASH; data: { asset: AssetDto } }
|
||||
| { type: EventType.ASSET_DELETE; data: { asset: AssetDto } }
|
||||
| { type: EventType.ALBUM_CREATE; data: { album: AlbumDto } }
|
||||
| { type: EventType.ALBUM_UPDATE; data: { album: AlbumDto } }
|
||||
);
|
||||
|
||||
export type PluginConfig = Record<string, ConfigItem>;
|
||||
|
||||
export type ConfigItem = {
|
||||
name: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
} & { [K in Types]: { type: K; default?: InferType<K> } }[Types];
|
||||
|
||||
export type InferType<T extends Types> = T extends 'string'
|
||||
? string
|
||||
: T extends 'date'
|
||||
? Date
|
||||
: T extends 'number'
|
||||
? number
|
||||
: T extends 'boolean'
|
||||
? boolean
|
||||
: never;
|
||||
|
||||
type Types = 'string' | 'boolean' | 'number' | 'date';
|
||||
type MaybePromise<T = void> = Promise<T> | T;
|
||||
type IfRequired<T extends ConfigItem, Type> = T['required'] extends true ? Type : Type | undefined;
|
||||
type InferConfig<T> = T extends PluginConfig
|
||||
? {
|
||||
[K in keyof T]: IfRequired<T[K], InferType<T[K]['type']>>;
|
||||
}
|
||||
: never;
|
||||
|
||||
export enum ActionType {
|
||||
ASSET = 'asset',
|
||||
ALBUM = 'album',
|
||||
ALBUM_ASSET = 'album-asset',
|
||||
}
|
||||
|
||||
export enum EventType {
|
||||
ASSET_UPLOAD = 'asset.upload',
|
||||
ASSET_UPDATE = 'asset.update',
|
||||
ASSET_TRASH = 'asset.trash',
|
||||
ASSET_DELETE = 'asset.delete',
|
||||
ASSET_ARCHIVE = 'asset.archive',
|
||||
ASSET_UNARCHIVE = 'asset.unarchive',
|
||||
|
||||
ALBUM_CREATE = 'album.create',
|
||||
ALBUM_UPDATE = 'album.update',
|
||||
ALBUM_DELETE = 'album.delete',
|
||||
}
|
||||
|
||||
export type AssetDto = { id: string; type: 'asset' };
|
||||
export type AlbumDto = { id: string; type: 'album' };
|
||||
92
server/src/plugin_types.ts
Normal file
92
server/src/plugin_types.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import sdk from '../../open-api/typescript-sdk';
|
||||
|
||||
export type Plugin = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
filters: Filter[];
|
||||
// actions: Action[];
|
||||
};
|
||||
|
||||
export enum EntityType {
|
||||
Asset = 'asset',
|
||||
Album = 'album',
|
||||
}
|
||||
|
||||
type PluginItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
type: EntityType;
|
||||
configuration?: Config[];
|
||||
};
|
||||
|
||||
type FilterContext<C = Record<string, any>, D = any> = {
|
||||
api: {
|
||||
getAssetAlbums: (assetId: string) => Promise<any[]>;
|
||||
};
|
||||
sdk: typeof sdk;
|
||||
config: C;
|
||||
};
|
||||
|
||||
type AssetFilter = {
|
||||
type: EntityType.Asset;
|
||||
filter: (ctx: FilterContext, input: { asset: { id: string } }) => Promise<boolean>;
|
||||
};
|
||||
|
||||
type AlbumFilter = {
|
||||
type: EntityType.Album;
|
||||
filter: (ctx: FilterContext, input: { album: { id: string; name: string } }) => Promise<boolean>;
|
||||
};
|
||||
|
||||
export type Filter = PluginItem & (AssetFilter | AlbumFilter);
|
||||
|
||||
export type Config = {
|
||||
key: string;
|
||||
type: PluginConfigType;
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
export type PluginConfigType = 'string' | 'number' | 'boolean' | 'date' | 'albumId' | 'assetId';
|
||||
|
||||
const authenticate = (ctx: FilterContext) => {
|
||||
const
|
||||
sdk.init()
|
||||
|
||||
}
|
||||
|
||||
export const corePlugin: Plugin = {
|
||||
id: 'immich',
|
||||
name: 'Immich Core Plugin',
|
||||
description: 'Core actions and filters for workflows',
|
||||
filters: [
|
||||
{
|
||||
id: 'core.notInAnyAlbum',
|
||||
name: 'Not in any album',
|
||||
description: 'Filters assets that are not in any album',
|
||||
type: EntityType.Asset,
|
||||
async filter(ctx, { asset }) {
|
||||
const albums = await ctx.sdk.getAllAlbums({ assetId: asset.id });
|
||||
return albums.length === 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.notInAlbum',
|
||||
name: 'Not in an album',
|
||||
description: 'Run on assets not in the specified album',
|
||||
type: EntityType.Asset,
|
||||
configuration: [
|
||||
{
|
||||
key: 'albumId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
async filter(ctx, { asset }) {
|
||||
// missing api to check if an asset is in an album
|
||||
const albums = await ctx.sdk.getAllAlbums({ assetId: asset.id });
|
||||
return !!albums.find((album) => album.id === ctx.config.albumId);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
55
server/src/plugins/asset.plugin.ts
Normal file
55
server/src/plugins/asset.plugin.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ActionType, AssetDto, Plugin, PluginContext } from 'src/interfaces/plugin.interface';
|
||||
|
||||
const onAsset = async (ctx: PluginContext, asset: AssetDto) => {
|
||||
await ctx.updateAsset({ id: asset.id, isArchived: true });
|
||||
};
|
||||
|
||||
export const plugin: Plugin = {
|
||||
version: 1,
|
||||
id: 'immich-plugins',
|
||||
name: 'Asset Plugin',
|
||||
description: 'Immich asset plugin',
|
||||
actions: [
|
||||
{
|
||||
id: 'asset.favorite',
|
||||
name: '',
|
||||
type: ActionType.ASSET,
|
||||
description: '',
|
||||
onAction: async (ctx, asset) => {
|
||||
await ctx.updateAsset({ id: asset.id, isArchived: false });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'asset.unfavorite',
|
||||
name: '',
|
||||
type: ActionType.ASSET,
|
||||
description: '',
|
||||
onAction: () => {
|
||||
console.log('Unfavorite');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'asset.action',
|
||||
name: '',
|
||||
type: ActionType.ASSET,
|
||||
description: '',
|
||||
onAction: (ctx, asset) => onAsset(ctx, asset),
|
||||
},
|
||||
{
|
||||
id: 'album-asset.action',
|
||||
name: '',
|
||||
type: ActionType.ALBUM_ASSET,
|
||||
description: '',
|
||||
onAction: (ctx, { asset }) => onAsset(ctx, asset),
|
||||
},
|
||||
{
|
||||
id: 'asset.unarchive',
|
||||
name: '',
|
||||
type: ActionType.ASSET,
|
||||
description: '',
|
||||
onAction: () => {
|
||||
console.log('Unarchive');
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -33,11 +33,6 @@ type Item<T extends EmitEvent> = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
type AssetCreateV1 = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
};
|
||||
|
||||
type EventMap = {
|
||||
// app events
|
||||
AppBootstrap: [];
|
||||
@@ -58,7 +53,6 @@ type EventMap = {
|
||||
AlbumInvite: [{ id: string; userId: string }];
|
||||
|
||||
// asset events
|
||||
AssetCreate: [{ asset: AssetCreateV1 }];
|
||||
AssetTag: [{ assetId: string }];
|
||||
AssetUntag: [{ assetId: string }];
|
||||
AssetHide: [{ assetId: string; userId: string }];
|
||||
|
||||
@@ -27,6 +27,7 @@ import { NotificationRepository } from 'src/repositories/notification.repository
|
||||
import { OAuthRepository } from 'src/repositories/oauth.repository';
|
||||
import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
import { PluginRepository } from 'src/repositories/plugin.repository';
|
||||
import { ProcessRepository } from 'src/repositories/process.repository';
|
||||
import { SearchRepository } from 'src/repositories/search.repository';
|
||||
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||
@@ -44,7 +45,6 @@ import { TrashRepository } from 'src/repositories/trash.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
|
||||
export const repositories = [
|
||||
AccessRepository,
|
||||
ActivityRepository,
|
||||
@@ -76,6 +76,7 @@ export const repositories = [
|
||||
PartnerRepository,
|
||||
PersonRepository,
|
||||
ProcessRepository,
|
||||
PluginRepository,
|
||||
SearchRepository,
|
||||
SessionRepository,
|
||||
ServerInfoRepository,
|
||||
|
||||
@@ -84,7 +84,6 @@ export class MetadataRepository {
|
||||
numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength', 'FileSize'],
|
||||
/* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */
|
||||
geoTz: (lat, lon) => geotz.find(lat, lon)[0],
|
||||
geolocation: true,
|
||||
// Enable exiftool LFS to parse metadata for files larger than 2GB.
|
||||
readArgs: ['-api', 'largefilesupport=1'],
|
||||
writeArgs: ['-api', 'largefilesupport=1', '-overwrite_original'],
|
||||
|
||||
83
server/src/repositories/plugin.repository.ts
Normal file
83
server/src/repositories/plugin.repository.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, Updateable } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { PluginLike } from 'src/interfaces/plugin.interface';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { PluginTable } from 'src/schema/tables/plugin.table';
|
||||
|
||||
type PluginSearchOptions = {
|
||||
id?: string;
|
||||
namespace?: string;
|
||||
version?: number;
|
||||
name?: string;
|
||||
isEnabled?: boolean;
|
||||
isInstalled?: boolean;
|
||||
isTrusted?: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PluginRepository {
|
||||
constructor(
|
||||
@InjectKysely() private db: Kysely<DB>,
|
||||
private logger: LoggingRepository,
|
||||
) {
|
||||
this.logger.setContext(PluginRepository.name);
|
||||
}
|
||||
|
||||
search(options: PluginSearchOptions) {
|
||||
return this.db
|
||||
.selectFrom('plugin')
|
||||
.select([
|
||||
'id',
|
||||
'packageId',
|
||||
'version',
|
||||
'name',
|
||||
'description',
|
||||
'isEnabled',
|
||||
'isInstalled',
|
||||
'isTrusted',
|
||||
'requirePath',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'deletedAt',
|
||||
])
|
||||
.$if(!!options.id, (qb) => qb.where('id', '=', options.id!))
|
||||
.$if(!!options.version, (qb) => qb.where('version', '=', options.version!))
|
||||
.$if(!!options.name, (qb) => qb.where('name', '=', options.name!))
|
||||
.$if(!!options.isEnabled, (qb) => qb.where('isEnabled', '=', options.isEnabled!))
|
||||
.$if(!!options.isInstalled, (qb) => qb.where('isInstalled', '=', options.isInstalled!))
|
||||
.$if(!!options.isTrusted, (qb) => qb.where('isTrusted', '=', options.isTrusted!))
|
||||
.execute();
|
||||
}
|
||||
|
||||
create(dto: Insertable<PluginTable>) {
|
||||
return this.db.insertInto('plugin').values(dto).returningAll().executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.db.selectFrom('plugin').where('id', '=', id).executeTakeFirst();
|
||||
}
|
||||
|
||||
update(dto: Updateable<PluginTable>) {
|
||||
return this.db.updateTable('plugin').set(dto).returningAll().executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.db.deleteFrom('plugin').where('id', '=', id).execute();
|
||||
}
|
||||
|
||||
async download(url: string, downloadPath: string): Promise<void> {
|
||||
try {
|
||||
const { json } = await fetch(url);
|
||||
await writeFile(downloadPath, await json());
|
||||
} catch (error) {
|
||||
this.logger.error(`Error downloading the plugin from ${url}. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
load(pluginPath: string): Promise<PluginLike> {
|
||||
return import(pluginPath);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table';
|
||||
import { PartnerTable } from 'src/schema/tables/partner.table';
|
||||
import { PersonAuditTable } from 'src/schema/tables/person-audit.table';
|
||||
import { PersonTable } from 'src/schema/tables/person.table';
|
||||
import { PluginTable } from 'src/schema/tables/plugin.table';
|
||||
import { SessionTable } from 'src/schema/tables/session.table';
|
||||
import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
@@ -105,6 +106,7 @@ export class ImmichDatabase {
|
||||
PartnerTable,
|
||||
PersonTable,
|
||||
PersonAuditTable,
|
||||
PluginTable,
|
||||
SessionTable,
|
||||
SharedLinkAssetTable,
|
||||
SharedLinkTable,
|
||||
@@ -202,6 +204,8 @@ export interface DB {
|
||||
person: PersonTable;
|
||||
person_audit: PersonAuditTable;
|
||||
|
||||
plugin: PluginTable;
|
||||
|
||||
session: SessionTable;
|
||||
session_sync_checkpoint: SessionSyncCheckpointTable;
|
||||
|
||||
|
||||
61
server/src/schema/tables/plugin.table.ts
Normal file
61
server/src/schema/tables/plugin.table.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Insertable } from 'kysely';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Generated,
|
||||
PrimaryGeneratedColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
|
||||
const plugin: Insertable<PluginTable> = {
|
||||
version: 1,
|
||||
id: '123',
|
||||
name: 'Immich Core Plugin',
|
||||
description: 'Core plugins for Immich',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
packageId: 'immich-plugin-',
|
||||
};
|
||||
|
||||
@Table('plugins')
|
||||
export class PluginTable {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: Generated<string>;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt!: Date | null;
|
||||
|
||||
@Column({ unique: true })
|
||||
packageId!: string;
|
||||
|
||||
@Column()
|
||||
version!: number;
|
||||
|
||||
@Column()
|
||||
name!: string;
|
||||
|
||||
@Column()
|
||||
description!: string;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
isEnabled!: Generated<boolean>;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isInstalled!: Generated<boolean>;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isTrusted!: Generated<boolean>;
|
||||
|
||||
@Column({ nullable: true })
|
||||
requirePath!: string | null;
|
||||
}
|
||||
@@ -426,9 +426,6 @@ export class AssetMediaService extends BaseService {
|
||||
}
|
||||
await this.storageRepository.utimes(file.originalPath, new Date(), new Date(dto.fileModifiedAt));
|
||||
await this.assetRepository.upsertExif({ assetId: asset.id, fileSizeInByte: file.size });
|
||||
|
||||
await this.eventRepository.emit('AssetCreate', { asset });
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.AssetExtractMetadata, data: { id: asset.id, source: 'upload' } });
|
||||
|
||||
return asset;
|
||||
|
||||
@@ -34,6 +34,7 @@ import { NotificationRepository } from 'src/repositories/notification.repository
|
||||
import { OAuthRepository } from 'src/repositories/oauth.repository';
|
||||
import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
import { PluginRepository } from 'src/repositories/plugin.repository';
|
||||
import { ProcessRepository } from 'src/repositories/process.repository';
|
||||
import { SearchRepository } from 'src/repositories/search.repository';
|
||||
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||
@@ -138,6 +139,7 @@ export class BaseService {
|
||||
protected oauthRepository: OAuthRepository,
|
||||
protected partnerRepository: PartnerRepository,
|
||||
protected personRepository: PersonRepository,
|
||||
protected pluginRepository: PluginRepository,
|
||||
protected processRepository: ProcessRepository,
|
||||
protected searchRepository: SearchRepository,
|
||||
protected serverInfoRepository: ServerInfoRepository,
|
||||
|
||||
@@ -41,6 +41,7 @@ import { UserAdminService } from 'src/services/user-admin.service';
|
||||
import { UserService } from 'src/services/user.service';
|
||||
import { VersionService } from 'src/services/version.service';
|
||||
import { ViewService } from 'src/services/view.service';
|
||||
import { WorkflowService } from 'src/services/workflow.service';
|
||||
|
||||
export const services = [
|
||||
ApiKeyService,
|
||||
@@ -86,4 +87,5 @@ export const services = [
|
||||
UserService,
|
||||
VersionService,
|
||||
ViewService,
|
||||
WorkflowService,
|
||||
];
|
||||
|
||||
@@ -447,10 +447,7 @@ export class MetadataService extends BaseService {
|
||||
* For RAW images in the CR2 or RAF format, the "ImageSize" value seems to be correct,
|
||||
* but ImageWidth and ImageHeight are not correct (they contain the dimensions of the preview image).
|
||||
*/
|
||||
let [width, height] =
|
||||
exifTags.ImageSize?.toString()
|
||||
?.split('x')
|
||||
?.map((dim) => Number.parseInt(dim) || undefined) ?? [];
|
||||
let [width, height] = exifTags.ImageSize?.split('x').map((dim) => Number.parseInt(dim) || undefined) || [];
|
||||
if (!width || !height) {
|
||||
[width, height] = [exifTags.ImageWidth, exifTags.ImageHeight];
|
||||
}
|
||||
|
||||
@@ -1,31 +1,42 @@
|
||||
import { CurrentPlugin, newPlugin } from '@extism/extism';
|
||||
import { Updateable } from 'kysely';
|
||||
import { resolve } from 'node:path';
|
||||
import { OnEvent } from 'src/decorators';
|
||||
import { ArgOf } from 'src/repositories/event.repository';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { Plugin } from 'src/database';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { mapPlugin, PluginSearchDto, PluginUpdateDto } from 'src/dtos/plugin.dto';
|
||||
import { Permission } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
|
||||
export class PluginService extends BaseService {
|
||||
@OnEvent({ name: 'AssetCreate' })
|
||||
async handleAssetCreate({ asset }: ArgOf<'AssetCreate'>) {
|
||||
console.log(`PluginService.handleAssetCreate: ${asset.id}`);
|
||||
const corePath = resolve('../plugins/dist/plugin.wasm');
|
||||
const plugin = await newPlugin(corePath, {
|
||||
useWasi: true,
|
||||
functions: {
|
||||
'extism:host/user': {
|
||||
updateAsset: (cp: CurrentPlugin, offs: bigint) => this.updateAsset(JSON.parse(cp.read(offs)!.text())),
|
||||
},
|
||||
},
|
||||
});
|
||||
const plugins: Plugin[] = [
|
||||
{
|
||||
id: '123',
|
||||
name: 'Immich Core Plugin',
|
||||
description: 'Core plugins for Immich',
|
||||
version: 1,
|
||||
isEnabled: true,
|
||||
isInstalled: true,
|
||||
isTrusted: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
packageId: 'immich-plugin-',
|
||||
},
|
||||
];
|
||||
|
||||
const event = { asset };
|
||||
await plugin.call('archiveAssetAction', JSON.stringify(event));
|
||||
export class PluginService extends BaseService {
|
||||
async search(auth: AuthDto, dto: PluginSearchDto) {
|
||||
await this.requireAccess({ auth, permission: Permission.PluginRead, ids: [] });
|
||||
// return this.pluginRepository.search(dto);
|
||||
|
||||
return plugins.map(mapPlugin);
|
||||
}
|
||||
|
||||
async updateAsset(asset: Updateable<AssetTable> & { id: string }) {
|
||||
console.log(`Updating asset ${asset.id} -- ${JSON.stringify({ ...asset, id: undefined })}`);
|
||||
await this.assetRepository.update(asset);
|
||||
async update(auth: AuthDto, id: string, dto: PluginUpdateDto) {
|
||||
await this.requireAccess({ auth, permission: Permission.PluginUpdate, ids: [id] });
|
||||
return this.pluginRepository.update({
|
||||
id,
|
||||
isEnabled: dto.isEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(auth: AuthDto, id: string) {
|
||||
await this.requireAccess({ auth, permission: Permission.PluginUpdate, ids: [id] });
|
||||
await this.pluginRepository.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
31
server/src/services/workflow.service.ts
Normal file
31
server/src/services/workflow.service.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PluginLike } from 'src/interfaces/plugin.interface';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkflowService extends BaseService {
|
||||
private plugins?: PluginLike[];
|
||||
|
||||
async init(): Promise<void> {
|
||||
const activePlugins = await this.pluginRepository.search({ isEnabled: true });
|
||||
const installPaths = activePlugins.map((p) => p.requirePath).filter(Boolean) as string[];
|
||||
this.plugins = await Promise.all(installPaths.map((path) => this.pluginRepository.load(path!)));
|
||||
}
|
||||
|
||||
// async register() {
|
||||
// const plugins = ['/src/abc'];
|
||||
// for (const pluginModule of plugins) {
|
||||
// // eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// try {
|
||||
// const plugin: Plugin = ;
|
||||
// const actions = await plugin.register();
|
||||
// for (const action of actions) {
|
||||
// this.actions[action.id] = action;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error(`Unable to load module: ${pluginModule}`, error);
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
12
server/src/utils/plugin.ts
Normal file
12
server/src/utils/plugin.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { AssetDto, EventType, OnAction, PluginConfig } from 'src/interfaces/plugin.interface';
|
||||
|
||||
export const createPluginAction = <T extends PluginConfig | undefined = undefined>(options: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
events?: EventType[];
|
||||
config?: T;
|
||||
}) => ({
|
||||
addHandler: (onAction: OnAction<T>) => ({ ...options, onAction }),
|
||||
onAsset: (onAction: OnAction<T, AssetDto>) => ({ ...options, onAction }),
|
||||
});
|
||||
@@ -25,6 +25,7 @@ export enum AppRoute {
|
||||
ADMIN_STATS = '/admin/server-status',
|
||||
ADMIN_JOBS = '/admin/jobs-status',
|
||||
ADMIN_REPAIR = '/admin/repair',
|
||||
ADMIN_PLUGINS = '/admin/plugins',
|
||||
|
||||
ALBUMS = '/albums',
|
||||
LIBRARIES = '/libraries',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { NavbarItem } from '@immich/ui';
|
||||
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync } from '@mdi/js';
|
||||
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiConnection, mdiServer, mdiSync } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<div class="flex flex-col pt-8 pe-4 gap-1">
|
||||
<NavbarItem title={$t('users')} href={AppRoute.ADMIN_USERS} icon={mdiAccountMultipleOutline} />
|
||||
<NavbarItem title={$t('jobs')} href={AppRoute.ADMIN_JOBS} icon={mdiSync} />
|
||||
<NavbarItem title={$t('plugins')} href={AppRoute.ADMIN_PLUGINS} icon={mdiConnection} />
|
||||
<NavbarItem title={$t('settings')} href={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
|
||||
<NavbarItem title={$t('external_libraries')} href={AppRoute.ADMIN_LIBRARY_MANAGEMENT} icon={mdiBookshelf} />
|
||||
<NavbarItem title={$t('server_stats')} href={AppRoute.ADMIN_STATS} icon={mdiServer} />
|
||||
|
||||
54
web/src/routes/admin/plugins/+page.svelte
Normal file
54
web/src/routes/admin/plugins/+page.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
|
||||
import { Button, Icon, Switch } from '@immich/ui';
|
||||
import { mdiCheckDecagram, mdiWrench } from '@mdi/js';
|
||||
import { range } from 'lodash-es';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
type Props = {
|
||||
data: PageData;
|
||||
};
|
||||
|
||||
const { data }: Props = $props();
|
||||
|
||||
const plugins = range(0, 8).map((index) => ({
|
||||
name: `Plugin-${index}`,
|
||||
description: `Plugin ${index} is awesome because it can do x and even y!`,
|
||||
isEnabled: Math.random() < 0.5,
|
||||
isInstalled: Math.random() < 0.5,
|
||||
isOfficial: Math.random() < 0.5,
|
||||
version: 1,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<AdminPageLayout title={data.meta.title}>
|
||||
{#snippet buttons()}
|
||||
<div class="flex gap-2 items-center justify-center">
|
||||
<Button leadingIcon={mdiWrench} onclick={() => console.log('clicked')}>Test</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<section id="setting-content" class="flex place-content-center sm:mx-4">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
{#each plugins as plugin, i (i)}
|
||||
<section
|
||||
class="flex flex-col gap-4 justify-between dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl p-4"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="m-0 items-start flex gap-2">
|
||||
{plugin.name}
|
||||
{#if plugin.isOfficial}
|
||||
<Icon icon={mdiCheckDecagram} size="18" class="text-success" />
|
||||
{/if}
|
||||
<div class="place-self-end justify-self-end justify-end self-end">Version {plugin.version}</div>
|
||||
</h1>
|
||||
|
||||
<p class="m-0 text-sm text-gray-600 dark:text-gray-300">{plugin.description}</p>
|
||||
</div>
|
||||
<div class="flex">Is {plugin.isInstalled ? '' : 'not '}installed</div>
|
||||
<Switch checked={plugin.isEnabled} id={plugin.name} title="Enabled" />
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</AdminPageLayout>
|
||||
16
web/src/routes/admin/plugins/+page.ts
Normal file
16
web/src/routes/admin/plugins/+page.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url, { admin: true });
|
||||
const plugins = [];
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
plugins,
|
||||
meta: {
|
||||
title: $t('plugins'),
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
Reference in New Issue
Block a user