mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:06:56 -04:00
Merge branch 'main' into main
This commit is contained in:
commit
5c927856b1
@ -73,14 +73,14 @@ You can edit the link properties, options include;
|
|||||||
- **Allow public user to download -** whether to allow whoever has the link to download all the images or a certain image (optional).
|
- **Allow public user to download -** whether to allow whoever has the link to download all the images or a certain image (optional).
|
||||||
- **Allow public user to upload -** whether to allow whoever has the link to upload assets to the album (optional).
|
- **Allow public user to upload -** whether to allow whoever has the link to upload assets to the album (optional).
|
||||||
:::info
|
:::info
|
||||||
whoever has the link and have uploaded files cannot delete them.
|
Whoever has the link and have uploaded files cannot delete them.
|
||||||
:::
|
:::
|
||||||
- **Expire after -** adding an expiration date to the link (optional).
|
- **Expire after -** adding an expiration date to the link (optional).
|
||||||
|
|
||||||
## Share Specific Assets
|
## Share Specific Assets
|
||||||
|
|
||||||
A user can share specific assets without linking them to a specific album.
|
A user can share specific assets without linking them to a specific album.
|
||||||
in order to do so;
|
In order to do this:
|
||||||
|
|
||||||
1. Go to the timeline
|
1. Go to the timeline
|
||||||
2. Select the assets (Shift can be used for multiple selection)
|
2. Select the assets (Shift can be used for multiple selection)
|
||||||
|
@ -4,7 +4,7 @@ sidebar_position: 10
|
|||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
Hardware and software requirements for Immich
|
Hardware and software requirements for Immich:
|
||||||
|
|
||||||
## Software
|
## Software
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ width="70%"
|
|||||||
alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
|
3. Select the cogwheel ⚙️ next to Immich and click "**Edit Stack**"
|
||||||
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default. Note that Unraid v6.12.10 uses version 24.0.9 of the Docker Engine, which does not support healthcheck `start_interval` as defined in the `database` service of the Docker compose file (version 25 or higher is needed). This parameter defines an initial waiting period before starting health checks, to give the container time to start up. Commenting out the `start_interval` and `start_period` parameters will allow the containers to start up normally. The only downside to this is that the database container will not receive an initial health check until `interval` time has passed.
|
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default. Note that Unraid v6.12.10 uses version 24.0.9 of the Docker Engine, which does not support healthcheck `start_interval` as defined in the `database` service of the Docker compose file (version 25 or higher is needed). This parameter defines an initial waiting period before starting health checks, to give the container time to start up. Commenting out the `start_interval` and `start_period` parameters will allow the containers to start up normally. The only downside to this is that the database container will not receive an initial health check until `interval` time has passed.
|
||||||
|
|
||||||
<details >
|
<details >
|
||||||
@ -130,7 +130,7 @@ For more information on how to use the application once installed, please refer
|
|||||||
|
|
||||||
## Updating Steps
|
## Updating Steps
|
||||||
|
|
||||||
Updating is extremely easy however it's important to be aware that containers managed via the Docker Compose Manager plugin do not integrate with Unraid's native dockerman ui, the label "_update ready_" will always be present on containers installed via the Docker Compose Manager.
|
Updating is extremely easy however it's important to be aware that containers managed via the Docker Compose Manager plugin do not integrate with Unraid's native dockerman UI, the label "_update ready_" will always be present on containers installed via the Docker Compose Manager.
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src={require('./img/unraid09.png').default}
|
src={require('./img/unraid09.png').default}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 1.5 MiB |
@ -1,12 +1,12 @@
|
|||||||
import { LoginResponseDto, createApiKey } from '@immich/sdk';
|
import { LoginResponseDto, Permission, createApiKey } from '@immich/sdk';
|
||||||
import { createUserDto, uuidDto } from 'src/fixtures';
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, asBearerAuth, utils } from 'src/utils';
|
import { app, asBearerAuth, utils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const create = (accessToken: string) =>
|
const create = (accessToken: string, permissions: Permission[]) =>
|
||||||
createApiKey({ apiKeyCreateDto: { name: 'api key' } }, { headers: asBearerAuth(accessToken) });
|
createApiKey({ apiKeyCreateDto: { name: 'api key', permissions } }, { headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
describe('/api-keys', () => {
|
describe('/api-keys', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
@ -30,15 +30,65 @@ describe('/api-keys', () => {
|
|||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not work without permission', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyRead]);
|
||||||
|
const { status, body } = await request(app).post('/api-keys').set('x-api-key', secret).send({ name: 'API Key' });
|
||||||
|
expect(status).toBe(403);
|
||||||
|
expect(body).toEqual(errorDto.missingPermission('apiKey.create'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with apiKey.create', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate, Permission.ApiKeyRead]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({
|
||||||
|
name: 'API Key',
|
||||||
|
permissions: [Permission.ApiKeyRead],
|
||||||
|
});
|
||||||
|
expect(body).toEqual({
|
||||||
|
secret: expect.any(String),
|
||||||
|
apiKey: {
|
||||||
|
id: expect.any(String),
|
||||||
|
name: 'API Key',
|
||||||
|
permissions: [Permission.ApiKeyRead],
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(status).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create an api key with all permissions', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ name: 'API Key', permissions: [Permission.All] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Cannot grant permissions you do not have'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create an api key with more permissions', async () => {
|
||||||
|
const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate]);
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/api-keys')
|
||||||
|
.set('x-api-key', secret)
|
||||||
|
.send({ name: 'API Key', permissions: [Permission.ApiKeyRead] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Cannot grant permissions you do not have'));
|
||||||
|
});
|
||||||
|
|
||||||
it('should create an api key', async () => {
|
it('should create an api key', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post('/api-keys')
|
.post('/api-keys')
|
||||||
.send({ name: 'API Key' })
|
.send({ name: 'API Key', permissions: [Permission.All] })
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
apiKey: {
|
apiKey: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
name: 'API Key',
|
name: 'API Key',
|
||||||
|
permissions: [Permission.All],
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
},
|
},
|
||||||
@ -63,9 +113,9 @@ describe('/api-keys', () => {
|
|||||||
|
|
||||||
it('should return a list of api keys', async () => {
|
it('should return a list of api keys', async () => {
|
||||||
const [{ apiKey: apiKey1 }, { apiKey: apiKey2 }, { apiKey: apiKey3 }] = await Promise.all([
|
const [{ apiKey: apiKey1 }, { apiKey: apiKey2 }, { apiKey: apiKey3 }] = await Promise.all([
|
||||||
create(admin.accessToken),
|
create(admin.accessToken, [Permission.All]),
|
||||||
create(admin.accessToken),
|
create(admin.accessToken, [Permission.All]),
|
||||||
create(admin.accessToken),
|
create(admin.accessToken, [Permission.All]),
|
||||||
]);
|
]);
|
||||||
const { status, body } = await request(app).get('/api-keys').set('Authorization', `Bearer ${admin.accessToken}`);
|
const { status, body } = await request(app).get('/api-keys').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(body).toHaveLength(3);
|
expect(body).toHaveLength(3);
|
||||||
@ -82,7 +132,7 @@ describe('/api-keys', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should require authorization', async () => {
|
it('should require authorization', async () => {
|
||||||
const { apiKey } = await create(user.accessToken);
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/api-keys/${apiKey.id}`)
|
.get(`/api-keys/${apiKey.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
@ -99,7 +149,7 @@ describe('/api-keys', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should get api key details', async () => {
|
it('should get api key details', async () => {
|
||||||
const { apiKey } = await create(user.accessToken);
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get(`/api-keys/${apiKey.id}`)
|
.get(`/api-keys/${apiKey.id}`)
|
||||||
.set('Authorization', `Bearer ${user.accessToken}`);
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
@ -107,6 +157,7 @@ describe('/api-keys', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
name: 'api key',
|
name: 'api key',
|
||||||
|
permissions: [Permission.All],
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
});
|
});
|
||||||
@ -121,7 +172,7 @@ describe('/api-keys', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should require authorization', async () => {
|
it('should require authorization', async () => {
|
||||||
const { apiKey } = await create(user.accessToken);
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/api-keys/${apiKey.id}`)
|
.put(`/api-keys/${apiKey.id}`)
|
||||||
.send({ name: 'new name' })
|
.send({ name: 'new name' })
|
||||||
@ -140,7 +191,7 @@ describe('/api-keys', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update api key details', async () => {
|
it('should update api key details', async () => {
|
||||||
const { apiKey } = await create(user.accessToken);
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/api-keys/${apiKey.id}`)
|
.put(`/api-keys/${apiKey.id}`)
|
||||||
.send({ name: 'new name' })
|
.send({ name: 'new name' })
|
||||||
@ -149,6 +200,7 @@ describe('/api-keys', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
name: 'new name',
|
name: 'new name',
|
||||||
|
permissions: [Permission.All],
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String),
|
updatedAt: expect.any(String),
|
||||||
});
|
});
|
||||||
@ -163,7 +215,7 @@ describe('/api-keys', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should require authorization', async () => {
|
it('should require authorization', async () => {
|
||||||
const { apiKey } = await create(user.accessToken);
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.delete(`/api-keys/${apiKey.id}`)
|
.delete(`/api-keys/${apiKey.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
@ -180,7 +232,7 @@ describe('/api-keys', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should delete an api key', async () => {
|
it('should delete an api key', async () => {
|
||||||
const { apiKey } = await create(user.accessToken);
|
const { apiKey } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status } = await request(app)
|
const { status } = await request(app)
|
||||||
.delete(`/api-keys/${apiKey.id}`)
|
.delete(`/api-keys/${apiKey.id}`)
|
||||||
.set('Authorization', `Bearer ${user.accessToken}`);
|
.set('Authorization', `Bearer ${user.accessToken}`);
|
||||||
@ -190,14 +242,14 @@ describe('/api-keys', () => {
|
|||||||
|
|
||||||
describe('authentication', () => {
|
describe('authentication', () => {
|
||||||
it('should work as a header', async () => {
|
it('should work as a header', async () => {
|
||||||
const { secret } = await create(admin.accessToken);
|
const { secret } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app).get('/api-keys').set('x-api-key', secret);
|
const { status, body } = await request(app).get('/api-keys').set('x-api-key', secret);
|
||||||
expect(body).toHaveLength(1);
|
expect(body).toHaveLength(1);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work as a query param', async () => {
|
it('should work as a query param', async () => {
|
||||||
const { secret } = await create(admin.accessToken);
|
const { secret } = await create(user.accessToken, [Permission.All]);
|
||||||
const { status, body } = await request(app).get(`/api-keys?apiKey=${secret}`);
|
const { status, body } = await request(app).get(`/api-keys?apiKey=${secret}`);
|
||||||
expect(body).toHaveLength(1);
|
expect(body).toHaveLength(1);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Permission } from '@immich/sdk';
|
||||||
import { stat } from 'node:fs/promises';
|
import { stat } from 'node:fs/promises';
|
||||||
import { app, immichCli, utils } from 'src/utils';
|
import { app, immichCli, utils } from 'src/utils';
|
||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
import { beforeEach, describe, expect, it } from 'vitest';
|
||||||
@ -29,7 +30,7 @@ describe(`immich login`, () => {
|
|||||||
|
|
||||||
it('should login and save auth.yml with 600', async () => {
|
it('should login and save auth.yml with 600', async () => {
|
||||||
const admin = await utils.adminSetup();
|
const admin = await utils.adminSetup();
|
||||||
const key = await utils.createApiKey(admin.accessToken);
|
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
|
||||||
const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]);
|
const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]);
|
||||||
expect(stdout.split('\n')).toEqual([
|
expect(stdout.split('\n')).toEqual([
|
||||||
'Logging in to http://127.0.0.1:2283/api',
|
'Logging in to http://127.0.0.1:2283/api',
|
||||||
@ -46,7 +47,7 @@ describe(`immich login`, () => {
|
|||||||
|
|
||||||
it('should login without /api in the url', async () => {
|
it('should login without /api in the url', async () => {
|
||||||
const admin = await utils.adminSetup();
|
const admin = await utils.adminSetup();
|
||||||
const key = await utils.createApiKey(admin.accessToken);
|
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
|
||||||
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]);
|
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]);
|
||||||
expect(stdout.split('\n')).toEqual([
|
expect(stdout.split('\n')).toEqual([
|
||||||
'Logging in to http://127.0.0.1:2283',
|
'Logging in to http://127.0.0.1:2283',
|
||||||
|
@ -13,6 +13,12 @@ export const errorDto = {
|
|||||||
message: expect.any(String),
|
message: expect.any(String),
|
||||||
correlationId: expect.any(String),
|
correlationId: expect.any(String),
|
||||||
},
|
},
|
||||||
|
missingPermission: (permission: string) => ({
|
||||||
|
error: 'Forbidden',
|
||||||
|
statusCode: 403,
|
||||||
|
message: `Missing required permission: ${permission}`,
|
||||||
|
correlationId: expect.any(String),
|
||||||
|
}),
|
||||||
wrongPassword: {
|
wrongPassword: {
|
||||||
error: 'Bad Request',
|
error: 'Bad Request',
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
CreateAlbumDto,
|
CreateAlbumDto,
|
||||||
CreateLibraryDto,
|
CreateLibraryDto,
|
||||||
MetadataSearchDto,
|
MetadataSearchDto,
|
||||||
|
Permission,
|
||||||
PersonCreateDto,
|
PersonCreateDto,
|
||||||
SharedLinkCreateDto,
|
SharedLinkCreateDto,
|
||||||
UserAdminCreateDto,
|
UserAdminCreateDto,
|
||||||
@ -279,8 +280,8 @@ export const utils = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
createApiKey: (accessToken: string) => {
|
createApiKey: (accessToken: string, permissions: Permission[]) => {
|
||||||
return createApiKey({ apiKeyCreateDto: { name: 'e2e' } }, { headers: asBearerAuth(accessToken) });
|
return createApiKey({ apiKeyCreateDto: { name: 'e2e', permissions } }, { headers: asBearerAuth(accessToken) });
|
||||||
},
|
},
|
||||||
|
|
||||||
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
|
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
|
||||||
@ -492,7 +493,7 @@ export const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cliLogin: async (accessToken: string) => {
|
cliLogin: async (accessToken: string) => {
|
||||||
const key = await utils.createApiKey(accessToken);
|
const key = await utils.createApiKey(accessToken, [Permission.All]);
|
||||||
await immichCli(['login', app, `${key.secret}`]);
|
await immichCli(['login', app, `${key.secret}`]);
|
||||||
return key.secret;
|
return key.secret;
|
||||||
},
|
},
|
||||||
|
@ -55,13 +55,13 @@
|
|||||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||||
"asset_list_settings_title": "Photo Grid",
|
"asset_list_settings_title": "Photo Grid",
|
||||||
"asset_restored_successfully": "Asset restored successfully",
|
"asset_restored_successfully": "Asset restored successfully",
|
||||||
|
"asset_viewer_settings_title": "Asset Viewer",
|
||||||
"assets_deleted_permanently": "{} asset(s) deleted permanently",
|
"assets_deleted_permanently": "{} asset(s) deleted permanently",
|
||||||
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server",
|
"assets_deleted_permanently_from_server": "{} asset(s) deleted permanently from the Immich server",
|
||||||
"assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device",
|
"assets_removed_permanently_from_device": "{} asset(s) removed permanently from your device",
|
||||||
"assets_restored_successfully": "{} asset(s) restored successfully",
|
"assets_restored_successfully": "{} asset(s) restored successfully",
|
||||||
"assets_trashed": "{} asset(s) trashed",
|
"assets_trashed": "{} asset(s) trashed",
|
||||||
"assets_trashed_from_server": "{} asset(s) trashed from the Immich server",
|
"assets_trashed_from_server": "{} asset(s) trashed from the Immich server",
|
||||||
"asset_viewer_settings_title": "Asset Viewer",
|
|
||||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
||||||
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
||||||
@ -173,6 +173,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Delete",
|
"control_bottom_app_bar_delete": "Delete",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||||
|
"control_bottom_app_bar_download": "Download",
|
||||||
"control_bottom_app_bar_edit": "Edit",
|
"control_bottom_app_bar_edit": "Edit",
|
||||||
"control_bottom_app_bar_edit_location": "Edit Location",
|
"control_bottom_app_bar_edit_location": "Edit Location",
|
||||||
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
||||||
@ -455,15 +456,18 @@
|
|||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||||
"setting_pages_app_bar_settings": "Settings",
|
"setting_pages_app_bar_settings": "Settings",
|
||||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
|
||||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||||
"setting_video_viewer_looping_title": "Looping",
|
"setting_video_viewer_looping_title": "Looping",
|
||||||
"setting_video_viewer_title": "Videos",
|
"setting_video_viewer_title": "Videos",
|
||||||
|
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||||
"share_add": "Add",
|
"share_add": "Add",
|
||||||
"share_add_photos": "Add photos",
|
"share_add_photos": "Add photos",
|
||||||
"share_add_title": "Add a title",
|
"share_add_title": "Add a title",
|
||||||
"share_assets_selected": "{} selected",
|
"share_assets_selected": "{} selected",
|
||||||
"share_create_album": "Create album",
|
"share_create_album": "Create album",
|
||||||
|
"share_dialog_preparing": "Preparing...",
|
||||||
|
"share_done": "Done",
|
||||||
|
"share_invite": "Invite to album",
|
||||||
"shared_album_activities_input_disable": "Comment is disabled",
|
"shared_album_activities_input_disable": "Comment is disabled",
|
||||||
"shared_album_activities_input_hint": "Say something",
|
"shared_album_activities_input_hint": "Say something",
|
||||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||||
@ -475,7 +479,6 @@
|
|||||||
"shared_album_section_people_action_remove_user": "Remove user from album",
|
"shared_album_section_people_action_remove_user": "Remove user from album",
|
||||||
"shared_album_section_people_owner_label": "Owner",
|
"shared_album_section_people_owner_label": "Owner",
|
||||||
"shared_album_section_people_title": "PEOPLE",
|
"shared_album_section_people_title": "PEOPLE",
|
||||||
"share_dialog_preparing": "Preparing...",
|
|
||||||
"shared_link_app_bar_title": "Shared Links",
|
"shared_link_app_bar_title": "Shared Links",
|
||||||
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
||||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||||
@ -521,14 +524,12 @@
|
|||||||
"shared_link_info_chip_upload": "Upload",
|
"shared_link_info_chip_upload": "Upload",
|
||||||
"shared_link_manage_links": "Manage Shared links",
|
"shared_link_manage_links": "Manage Shared links",
|
||||||
"shared_link_public_album": "Public album",
|
"shared_link_public_album": "Public album",
|
||||||
"share_done": "Done",
|
|
||||||
"share_invite": "Invite to album",
|
|
||||||
"sharing_page_album": "Shared albums",
|
"sharing_page_album": "Shared albums",
|
||||||
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
||||||
"sharing_page_empty_list": "EMPTY LIST",
|
"sharing_page_empty_list": "EMPTY LIST",
|
||||||
"sharing_silver_appbar_create_shared_album": "New shared album",
|
"sharing_silver_appbar_create_shared_album": "New shared album",
|
||||||
"sharing_silver_appbar_shared_links": "Shared links",
|
|
||||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
"sharing_silver_appbar_share_partner": "Share with partner",
|
||||||
|
"sharing_silver_appbar_shared_links": "Shared links",
|
||||||
"tab_controller_nav_library": "Library",
|
"tab_controller_nav_library": "Library",
|
||||||
"tab_controller_nav_photos": "Photos",
|
"tab_controller_nav_photos": "Photos",
|
||||||
"tab_controller_nav_search": "Search",
|
"tab_controller_nav_search": "Search",
|
||||||
|
@ -51,8 +51,8 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void stopScreenDarkenTimer() {
|
void stopScreenDarkenTimer() {
|
||||||
isScreenDarkened.value = false;
|
|
||||||
darkenScreenTimer.value?.cancel();
|
darkenScreenTimer.value?.cancel();
|
||||||
|
isScreenDarkened.value = false;
|
||||||
SystemChrome.setEnabledSystemUIMode(
|
SystemChrome.setEnabledSystemUIMode(
|
||||||
SystemUiMode.manual,
|
SystemUiMode.manual,
|
||||||
overlays: [
|
overlays: [
|
||||||
@ -75,8 +75,6 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
.watch(websocketProvider.notifier)
|
.watch(websocketProvider.notifier)
|
||||||
.stopListenToEvent('on_upload_success');
|
.stopListenToEvent('on_upload_success');
|
||||||
|
|
||||||
WakelockPlus.enable();
|
|
||||||
|
|
||||||
return () {
|
return () {
|
||||||
WakelockPlus.disable();
|
WakelockPlus.disable();
|
||||||
darkenScreenTimer.value?.cancel();
|
darkenScreenTimer.value?.cancel();
|
||||||
@ -102,8 +100,10 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
() {
|
() {
|
||||||
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
|
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
|
||||||
startScreenDarkenTimer();
|
startScreenDarkenTimer();
|
||||||
|
WakelockPlus.enable();
|
||||||
} else {
|
} else {
|
||||||
stopScreenDarkenTimer();
|
stopScreenDarkenTimer();
|
||||||
|
WakelockPlus.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -20,7 +20,7 @@ import 'package:isar/isar.dart';
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart' as pm;
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
final backupServiceProvider = Provider(
|
final backupServiceProvider = Provider(
|
||||||
@ -213,7 +213,7 @@ class BackupService {
|
|||||||
_appSetting.getSetting(AppSettingsEnum.ignoreIcloudAssets);
|
_appSetting.getSetting(AppSettingsEnum.ignoreIcloudAssets);
|
||||||
|
|
||||||
if (Platform.isAndroid &&
|
if (Platform.isAndroid &&
|
||||||
!(await Permission.accessMediaLocation.status).isGranted) {
|
!(await pm.Permission.accessMediaLocation.status).isGranted) {
|
||||||
// double check that permission is granted here, to guard against
|
// double check that permission is granted here, to guard against
|
||||||
// uploading corrupt assets without EXIF information
|
// uploading corrupt assets without EXIF information
|
||||||
_log.warning("Media location permission is not granted. "
|
_log.warning("Media location permission is not granted. "
|
||||||
|
@ -29,7 +29,8 @@ class LocalNotificationService {
|
|||||||
static const cancelUploadActionID = 'cancel_upload';
|
static const cancelUploadActionID = 'cancel_upload';
|
||||||
|
|
||||||
Future<void> setup() async {
|
Future<void> setup() async {
|
||||||
const androidSetting = AndroidInitializationSettings('notification_icon');
|
const androidSetting =
|
||||||
|
AndroidInitializationSettings('@drawable/notification_icon');
|
||||||
const iosSetting = DarwinInitializationSettings();
|
const iosSetting = DarwinInitializationSettings();
|
||||||
|
|
||||||
const initSettings =
|
const initSettings =
|
||||||
|
@ -366,8 +366,8 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
{
|
{
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: const Icon(Icons.download_outlined),
|
icon: const Icon(Icons.download_outlined),
|
||||||
label: 'download'.tr(),
|
label: 'control_bottom_app_bar_download'.tr(),
|
||||||
tooltip: 'download'.tr(),
|
tooltip: 'control_bottom_app_bar_download'.tr(),
|
||||||
): (_) => handleDownload(),
|
): (_) => handleDownload(),
|
||||||
},
|
},
|
||||||
if (isInAlbum)
|
if (isInAlbum)
|
||||||
|
1
mobile/openapi/README.md
generated
1
mobile/openapi/README.md
generated
@ -363,6 +363,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [PeopleResponseDto](doc//PeopleResponseDto.md)
|
- [PeopleResponseDto](doc//PeopleResponseDto.md)
|
||||||
- [PeopleUpdateDto](doc//PeopleUpdateDto.md)
|
- [PeopleUpdateDto](doc//PeopleUpdateDto.md)
|
||||||
- [PeopleUpdateItem](doc//PeopleUpdateItem.md)
|
- [PeopleUpdateItem](doc//PeopleUpdateItem.md)
|
||||||
|
- [Permission](doc//Permission.md)
|
||||||
- [PersonCreateDto](doc//PersonCreateDto.md)
|
- [PersonCreateDto](doc//PersonCreateDto.md)
|
||||||
- [PersonResponseDto](doc//PersonResponseDto.md)
|
- [PersonResponseDto](doc//PersonResponseDto.md)
|
||||||
- [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md)
|
- [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md)
|
||||||
|
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
@ -175,6 +175,7 @@ part 'model/path_type.dart';
|
|||||||
part 'model/people_response_dto.dart';
|
part 'model/people_response_dto.dart';
|
||||||
part 'model/people_update_dto.dart';
|
part 'model/people_update_dto.dart';
|
||||||
part 'model/people_update_item.dart';
|
part 'model/people_update_item.dart';
|
||||||
|
part 'model/permission.dart';
|
||||||
part 'model/person_create_dto.dart';
|
part 'model/person_create_dto.dart';
|
||||||
part 'model/person_response_dto.dart';
|
part 'model/person_response_dto.dart';
|
||||||
part 'model/person_statistics_response_dto.dart';
|
part 'model/person_statistics_response_dto.dart';
|
||||||
|
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
@ -407,6 +407,8 @@ class ApiClient {
|
|||||||
return PeopleUpdateDto.fromJson(value);
|
return PeopleUpdateDto.fromJson(value);
|
||||||
case 'PeopleUpdateItem':
|
case 'PeopleUpdateItem':
|
||||||
return PeopleUpdateItem.fromJson(value);
|
return PeopleUpdateItem.fromJson(value);
|
||||||
|
case 'Permission':
|
||||||
|
return PermissionTypeTransformer().decode(value);
|
||||||
case 'PersonCreateDto':
|
case 'PersonCreateDto':
|
||||||
return PersonCreateDto.fromJson(value);
|
return PersonCreateDto.fromJson(value);
|
||||||
case 'PersonResponseDto':
|
case 'PersonResponseDto':
|
||||||
|
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
@ -112,6 +112,9 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is PathType) {
|
if (value is PathType) {
|
||||||
return PathTypeTypeTransformer().encode(value).toString();
|
return PathTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is Permission) {
|
||||||
|
return PermissionTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
if (value is ReactionLevel) {
|
if (value is ReactionLevel) {
|
||||||
return ReactionLevelTypeTransformer().encode(value).toString();
|
return ReactionLevelTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
14
mobile/openapi/lib/model/api_key_create_dto.dart
generated
14
mobile/openapi/lib/model/api_key_create_dto.dart
generated
@ -14,6 +14,7 @@ class APIKeyCreateDto {
|
|||||||
/// Returns a new [APIKeyCreateDto] instance.
|
/// Returns a new [APIKeyCreateDto] instance.
|
||||||
APIKeyCreateDto({
|
APIKeyCreateDto({
|
||||||
this.name,
|
this.name,
|
||||||
|
this.permissions = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -24,17 +25,21 @@ class APIKeyCreateDto {
|
|||||||
///
|
///
|
||||||
String? name;
|
String? name;
|
||||||
|
|
||||||
|
List<Permission> permissions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is APIKeyCreateDto &&
|
bool operator ==(Object other) => identical(this, other) || other is APIKeyCreateDto &&
|
||||||
other.name == name;
|
other.name == name &&
|
||||||
|
_deepEquality.equals(other.permissions, permissions);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(name == null ? 0 : name!.hashCode);
|
(name == null ? 0 : name!.hashCode) +
|
||||||
|
(permissions.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'APIKeyCreateDto[name=$name]';
|
String toString() => 'APIKeyCreateDto[name=$name, permissions=$permissions]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -43,6 +48,7 @@ class APIKeyCreateDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'name'] = null;
|
// json[r'name'] = null;
|
||||||
}
|
}
|
||||||
|
json[r'permissions'] = this.permissions;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +61,7 @@ class APIKeyCreateDto {
|
|||||||
|
|
||||||
return APIKeyCreateDto(
|
return APIKeyCreateDto(
|
||||||
name: mapValueOfType<String>(json, r'name'),
|
name: mapValueOfType<String>(json, r'name'),
|
||||||
|
permissions: Permission.listFromJson(json[r'permissions']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -102,6 +109,7 @@ class APIKeyCreateDto {
|
|||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
|
'permissions',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
mobile/openapi/lib/model/api_key_response_dto.dart
generated
10
mobile/openapi/lib/model/api_key_response_dto.dart
generated
@ -16,6 +16,7 @@ class APIKeyResponseDto {
|
|||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
this.permissions = const [],
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -25,6 +26,8 @@ class APIKeyResponseDto {
|
|||||||
|
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
|
List<Permission> permissions;
|
||||||
|
|
||||||
DateTime updatedAt;
|
DateTime updatedAt;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -32,6 +35,7 @@ class APIKeyResponseDto {
|
|||||||
other.createdAt == createdAt &&
|
other.createdAt == createdAt &&
|
||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.name == name &&
|
other.name == name &&
|
||||||
|
_deepEquality.equals(other.permissions, permissions) &&
|
||||||
other.updatedAt == updatedAt;
|
other.updatedAt == updatedAt;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,16 +44,18 @@ class APIKeyResponseDto {
|
|||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(name.hashCode) +
|
(name.hashCode) +
|
||||||
|
(permissions.hashCode) +
|
||||||
(updatedAt.hashCode);
|
(updatedAt.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'APIKeyResponseDto[createdAt=$createdAt, id=$id, name=$name, updatedAt=$updatedAt]';
|
String toString() => 'APIKeyResponseDto[createdAt=$createdAt, id=$id, name=$name, permissions=$permissions, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'name'] = this.name;
|
json[r'name'] = this.name;
|
||||||
|
json[r'permissions'] = this.permissions;
|
||||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@ -65,6 +71,7 @@ class APIKeyResponseDto {
|
|||||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
name: mapValueOfType<String>(json, r'name')!,
|
name: mapValueOfType<String>(json, r'name')!,
|
||||||
|
permissions: Permission.listFromJson(json[r'permissions']),
|
||||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -116,6 +123,7 @@ class APIKeyResponseDto {
|
|||||||
'createdAt',
|
'createdAt',
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
|
'permissions',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
292
mobile/openapi/lib/model/permission.dart
generated
Normal file
292
mobile/openapi/lib/model/permission.dart
generated
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
|
class Permission {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const Permission._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const all = Permission._(r'all');
|
||||||
|
static const activityPeriodCreate = Permission._(r'activity.create');
|
||||||
|
static const activityPeriodRead = Permission._(r'activity.read');
|
||||||
|
static const activityPeriodUpdate = Permission._(r'activity.update');
|
||||||
|
static const activityPeriodDelete = Permission._(r'activity.delete');
|
||||||
|
static const activityPeriodStatistics = Permission._(r'activity.statistics');
|
||||||
|
static const apiKeyPeriodCreate = Permission._(r'apiKey.create');
|
||||||
|
static const apiKeyPeriodRead = Permission._(r'apiKey.read');
|
||||||
|
static const apiKeyPeriodUpdate = Permission._(r'apiKey.update');
|
||||||
|
static const apiKeyPeriodDelete = Permission._(r'apiKey.delete');
|
||||||
|
static const assetPeriodRead = Permission._(r'asset.read');
|
||||||
|
static const assetPeriodUpdate = Permission._(r'asset.update');
|
||||||
|
static const assetPeriodDelete = Permission._(r'asset.delete');
|
||||||
|
static const assetPeriodRestore = Permission._(r'asset.restore');
|
||||||
|
static const assetPeriodShare = Permission._(r'asset.share');
|
||||||
|
static const assetPeriodView = Permission._(r'asset.view');
|
||||||
|
static const assetPeriodDownload = Permission._(r'asset.download');
|
||||||
|
static const assetPeriodUpload = Permission._(r'asset.upload');
|
||||||
|
static const albumPeriodCreate = Permission._(r'album.create');
|
||||||
|
static const albumPeriodRead = Permission._(r'album.read');
|
||||||
|
static const albumPeriodUpdate = Permission._(r'album.update');
|
||||||
|
static const albumPeriodDelete = Permission._(r'album.delete');
|
||||||
|
static const albumPeriodStatistics = Permission._(r'album.statistics');
|
||||||
|
static const albumPeriodAddAsset = Permission._(r'album.addAsset');
|
||||||
|
static const albumPeriodRemoveAsset = Permission._(r'album.removeAsset');
|
||||||
|
static const albumPeriodShare = Permission._(r'album.share');
|
||||||
|
static const albumPeriodDownload = Permission._(r'album.download');
|
||||||
|
static const authDevicePeriodDelete = Permission._(r'authDevice.delete');
|
||||||
|
static const archivePeriodRead = Permission._(r'archive.read');
|
||||||
|
static const facePeriodCreate = Permission._(r'face.create');
|
||||||
|
static const facePeriodRead = Permission._(r'face.read');
|
||||||
|
static const facePeriodUpdate = Permission._(r'face.update');
|
||||||
|
static const facePeriodDelete = Permission._(r'face.delete');
|
||||||
|
static const libraryPeriodCreate = Permission._(r'library.create');
|
||||||
|
static const libraryPeriodRead = Permission._(r'library.read');
|
||||||
|
static const libraryPeriodUpdate = Permission._(r'library.update');
|
||||||
|
static const libraryPeriodDelete = Permission._(r'library.delete');
|
||||||
|
static const libraryPeriodStatistics = Permission._(r'library.statistics');
|
||||||
|
static const timelinePeriodRead = Permission._(r'timeline.read');
|
||||||
|
static const timelinePeriodDownload = Permission._(r'timeline.download');
|
||||||
|
static const memoryPeriodCreate = Permission._(r'memory.create');
|
||||||
|
static const memoryPeriodRead = Permission._(r'memory.read');
|
||||||
|
static const memoryPeriodUpdate = Permission._(r'memory.update');
|
||||||
|
static const memoryPeriodDelete = Permission._(r'memory.delete');
|
||||||
|
static const partnerPeriodCreate = Permission._(r'partner.create');
|
||||||
|
static const partnerPeriodRead = Permission._(r'partner.read');
|
||||||
|
static const partnerPeriodUpdate = Permission._(r'partner.update');
|
||||||
|
static const partnerPeriodDelete = Permission._(r'partner.delete');
|
||||||
|
static const personPeriodCreate = Permission._(r'person.create');
|
||||||
|
static const personPeriodRead = Permission._(r'person.read');
|
||||||
|
static const personPeriodUpdate = Permission._(r'person.update');
|
||||||
|
static const personPeriodDelete = Permission._(r'person.delete');
|
||||||
|
static const personPeriodStatistics = Permission._(r'person.statistics');
|
||||||
|
static const personPeriodMerge = Permission._(r'person.merge');
|
||||||
|
static const personPeriodReassign = Permission._(r'person.reassign');
|
||||||
|
static const sharedLinkPeriodCreate = Permission._(r'sharedLink.create');
|
||||||
|
static const sharedLinkPeriodRead = Permission._(r'sharedLink.read');
|
||||||
|
static const sharedLinkPeriodUpdate = Permission._(r'sharedLink.update');
|
||||||
|
static const sharedLinkPeriodDelete = Permission._(r'sharedLink.delete');
|
||||||
|
static const systemConfigPeriodRead = Permission._(r'systemConfig.read');
|
||||||
|
static const systemConfigPeriodUpdate = Permission._(r'systemConfig.update');
|
||||||
|
static const systemMetadataPeriodRead = Permission._(r'systemMetadata.read');
|
||||||
|
static const systemMetadataPeriodUpdate = Permission._(r'systemMetadata.update');
|
||||||
|
static const tagPeriodCreate = Permission._(r'tag.create');
|
||||||
|
static const tagPeriodRead = Permission._(r'tag.read');
|
||||||
|
static const tagPeriodUpdate = Permission._(r'tag.update');
|
||||||
|
static const tagPeriodDelete = Permission._(r'tag.delete');
|
||||||
|
static const adminPeriodUserPeriodCreate = Permission._(r'admin.user.create');
|
||||||
|
static const adminPeriodUserPeriodRead = Permission._(r'admin.user.read');
|
||||||
|
static const adminPeriodUserPeriodUpdate = Permission._(r'admin.user.update');
|
||||||
|
static const adminPeriodUserPeriodDelete = Permission._(r'admin.user.delete');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][Permission].
|
||||||
|
static const values = <Permission>[
|
||||||
|
all,
|
||||||
|
activityPeriodCreate,
|
||||||
|
activityPeriodRead,
|
||||||
|
activityPeriodUpdate,
|
||||||
|
activityPeriodDelete,
|
||||||
|
activityPeriodStatistics,
|
||||||
|
apiKeyPeriodCreate,
|
||||||
|
apiKeyPeriodRead,
|
||||||
|
apiKeyPeriodUpdate,
|
||||||
|
apiKeyPeriodDelete,
|
||||||
|
assetPeriodRead,
|
||||||
|
assetPeriodUpdate,
|
||||||
|
assetPeriodDelete,
|
||||||
|
assetPeriodRestore,
|
||||||
|
assetPeriodShare,
|
||||||
|
assetPeriodView,
|
||||||
|
assetPeriodDownload,
|
||||||
|
assetPeriodUpload,
|
||||||
|
albumPeriodCreate,
|
||||||
|
albumPeriodRead,
|
||||||
|
albumPeriodUpdate,
|
||||||
|
albumPeriodDelete,
|
||||||
|
albumPeriodStatistics,
|
||||||
|
albumPeriodAddAsset,
|
||||||
|
albumPeriodRemoveAsset,
|
||||||
|
albumPeriodShare,
|
||||||
|
albumPeriodDownload,
|
||||||
|
authDevicePeriodDelete,
|
||||||
|
archivePeriodRead,
|
||||||
|
facePeriodCreate,
|
||||||
|
facePeriodRead,
|
||||||
|
facePeriodUpdate,
|
||||||
|
facePeriodDelete,
|
||||||
|
libraryPeriodCreate,
|
||||||
|
libraryPeriodRead,
|
||||||
|
libraryPeriodUpdate,
|
||||||
|
libraryPeriodDelete,
|
||||||
|
libraryPeriodStatistics,
|
||||||
|
timelinePeriodRead,
|
||||||
|
timelinePeriodDownload,
|
||||||
|
memoryPeriodCreate,
|
||||||
|
memoryPeriodRead,
|
||||||
|
memoryPeriodUpdate,
|
||||||
|
memoryPeriodDelete,
|
||||||
|
partnerPeriodCreate,
|
||||||
|
partnerPeriodRead,
|
||||||
|
partnerPeriodUpdate,
|
||||||
|
partnerPeriodDelete,
|
||||||
|
personPeriodCreate,
|
||||||
|
personPeriodRead,
|
||||||
|
personPeriodUpdate,
|
||||||
|
personPeriodDelete,
|
||||||
|
personPeriodStatistics,
|
||||||
|
personPeriodMerge,
|
||||||
|
personPeriodReassign,
|
||||||
|
sharedLinkPeriodCreate,
|
||||||
|
sharedLinkPeriodRead,
|
||||||
|
sharedLinkPeriodUpdate,
|
||||||
|
sharedLinkPeriodDelete,
|
||||||
|
systemConfigPeriodRead,
|
||||||
|
systemConfigPeriodUpdate,
|
||||||
|
systemMetadataPeriodRead,
|
||||||
|
systemMetadataPeriodUpdate,
|
||||||
|
tagPeriodCreate,
|
||||||
|
tagPeriodRead,
|
||||||
|
tagPeriodUpdate,
|
||||||
|
tagPeriodDelete,
|
||||||
|
adminPeriodUserPeriodCreate,
|
||||||
|
adminPeriodUserPeriodRead,
|
||||||
|
adminPeriodUserPeriodUpdate,
|
||||||
|
adminPeriodUserPeriodDelete,
|
||||||
|
];
|
||||||
|
|
||||||
|
static Permission? fromJson(dynamic value) => PermissionTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<Permission> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <Permission>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = Permission.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [Permission] to String,
|
||||||
|
/// and [decode] dynamic data back to [Permission].
|
||||||
|
class PermissionTypeTransformer {
|
||||||
|
factory PermissionTypeTransformer() => _instance ??= const PermissionTypeTransformer._();
|
||||||
|
|
||||||
|
const PermissionTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(Permission data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a Permission.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
Permission? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data) {
|
||||||
|
case r'all': return Permission.all;
|
||||||
|
case r'activity.create': return Permission.activityPeriodCreate;
|
||||||
|
case r'activity.read': return Permission.activityPeriodRead;
|
||||||
|
case r'activity.update': return Permission.activityPeriodUpdate;
|
||||||
|
case r'activity.delete': return Permission.activityPeriodDelete;
|
||||||
|
case r'activity.statistics': return Permission.activityPeriodStatistics;
|
||||||
|
case r'apiKey.create': return Permission.apiKeyPeriodCreate;
|
||||||
|
case r'apiKey.read': return Permission.apiKeyPeriodRead;
|
||||||
|
case r'apiKey.update': return Permission.apiKeyPeriodUpdate;
|
||||||
|
case r'apiKey.delete': return Permission.apiKeyPeriodDelete;
|
||||||
|
case r'asset.read': return Permission.assetPeriodRead;
|
||||||
|
case r'asset.update': return Permission.assetPeriodUpdate;
|
||||||
|
case r'asset.delete': return Permission.assetPeriodDelete;
|
||||||
|
case r'asset.restore': return Permission.assetPeriodRestore;
|
||||||
|
case r'asset.share': return Permission.assetPeriodShare;
|
||||||
|
case r'asset.view': return Permission.assetPeriodView;
|
||||||
|
case r'asset.download': return Permission.assetPeriodDownload;
|
||||||
|
case r'asset.upload': return Permission.assetPeriodUpload;
|
||||||
|
case r'album.create': return Permission.albumPeriodCreate;
|
||||||
|
case r'album.read': return Permission.albumPeriodRead;
|
||||||
|
case r'album.update': return Permission.albumPeriodUpdate;
|
||||||
|
case r'album.delete': return Permission.albumPeriodDelete;
|
||||||
|
case r'album.statistics': return Permission.albumPeriodStatistics;
|
||||||
|
case r'album.addAsset': return Permission.albumPeriodAddAsset;
|
||||||
|
case r'album.removeAsset': return Permission.albumPeriodRemoveAsset;
|
||||||
|
case r'album.share': return Permission.albumPeriodShare;
|
||||||
|
case r'album.download': return Permission.albumPeriodDownload;
|
||||||
|
case r'authDevice.delete': return Permission.authDevicePeriodDelete;
|
||||||
|
case r'archive.read': return Permission.archivePeriodRead;
|
||||||
|
case r'face.create': return Permission.facePeriodCreate;
|
||||||
|
case r'face.read': return Permission.facePeriodRead;
|
||||||
|
case r'face.update': return Permission.facePeriodUpdate;
|
||||||
|
case r'face.delete': return Permission.facePeriodDelete;
|
||||||
|
case r'library.create': return Permission.libraryPeriodCreate;
|
||||||
|
case r'library.read': return Permission.libraryPeriodRead;
|
||||||
|
case r'library.update': return Permission.libraryPeriodUpdate;
|
||||||
|
case r'library.delete': return Permission.libraryPeriodDelete;
|
||||||
|
case r'library.statistics': return Permission.libraryPeriodStatistics;
|
||||||
|
case r'timeline.read': return Permission.timelinePeriodRead;
|
||||||
|
case r'timeline.download': return Permission.timelinePeriodDownload;
|
||||||
|
case r'memory.create': return Permission.memoryPeriodCreate;
|
||||||
|
case r'memory.read': return Permission.memoryPeriodRead;
|
||||||
|
case r'memory.update': return Permission.memoryPeriodUpdate;
|
||||||
|
case r'memory.delete': return Permission.memoryPeriodDelete;
|
||||||
|
case r'partner.create': return Permission.partnerPeriodCreate;
|
||||||
|
case r'partner.read': return Permission.partnerPeriodRead;
|
||||||
|
case r'partner.update': return Permission.partnerPeriodUpdate;
|
||||||
|
case r'partner.delete': return Permission.partnerPeriodDelete;
|
||||||
|
case r'person.create': return Permission.personPeriodCreate;
|
||||||
|
case r'person.read': return Permission.personPeriodRead;
|
||||||
|
case r'person.update': return Permission.personPeriodUpdate;
|
||||||
|
case r'person.delete': return Permission.personPeriodDelete;
|
||||||
|
case r'person.statistics': return Permission.personPeriodStatistics;
|
||||||
|
case r'person.merge': return Permission.personPeriodMerge;
|
||||||
|
case r'person.reassign': return Permission.personPeriodReassign;
|
||||||
|
case r'sharedLink.create': return Permission.sharedLinkPeriodCreate;
|
||||||
|
case r'sharedLink.read': return Permission.sharedLinkPeriodRead;
|
||||||
|
case r'sharedLink.update': return Permission.sharedLinkPeriodUpdate;
|
||||||
|
case r'sharedLink.delete': return Permission.sharedLinkPeriodDelete;
|
||||||
|
case r'systemConfig.read': return Permission.systemConfigPeriodRead;
|
||||||
|
case r'systemConfig.update': return Permission.systemConfigPeriodUpdate;
|
||||||
|
case r'systemMetadata.read': return Permission.systemMetadataPeriodRead;
|
||||||
|
case r'systemMetadata.update': return Permission.systemMetadataPeriodUpdate;
|
||||||
|
case r'tag.create': return Permission.tagPeriodCreate;
|
||||||
|
case r'tag.read': return Permission.tagPeriodRead;
|
||||||
|
case r'tag.update': return Permission.tagPeriodUpdate;
|
||||||
|
case r'tag.delete': return Permission.tagPeriodDelete;
|
||||||
|
case r'admin.user.create': return Permission.adminPeriodUserPeriodCreate;
|
||||||
|
case r'admin.user.read': return Permission.adminPeriodUserPeriodRead;
|
||||||
|
case r'admin.user.update': return Permission.adminPeriodUserPeriodUpdate;
|
||||||
|
case r'admin.user.delete': return Permission.adminPeriodUserPeriodDelete;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [PermissionTypeTransformer] instance.
|
||||||
|
static PermissionTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
@ -7135,8 +7135,17 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Permission"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required": [
|
||||||
|
"permissions"
|
||||||
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"APIKeyCreateResponseDto": {
|
"APIKeyCreateResponseDto": {
|
||||||
@ -7166,6 +7175,12 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Permission"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -7175,6 +7190,7 @@
|
|||||||
"createdAt",
|
"createdAt",
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
|
"permissions",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@ -9729,6 +9745,82 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"Permission": {
|
||||||
|
"enum": [
|
||||||
|
"all",
|
||||||
|
"activity.create",
|
||||||
|
"activity.read",
|
||||||
|
"activity.update",
|
||||||
|
"activity.delete",
|
||||||
|
"activity.statistics",
|
||||||
|
"apiKey.create",
|
||||||
|
"apiKey.read",
|
||||||
|
"apiKey.update",
|
||||||
|
"apiKey.delete",
|
||||||
|
"asset.read",
|
||||||
|
"asset.update",
|
||||||
|
"asset.delete",
|
||||||
|
"asset.restore",
|
||||||
|
"asset.share",
|
||||||
|
"asset.view",
|
||||||
|
"asset.download",
|
||||||
|
"asset.upload",
|
||||||
|
"album.create",
|
||||||
|
"album.read",
|
||||||
|
"album.update",
|
||||||
|
"album.delete",
|
||||||
|
"album.statistics",
|
||||||
|
"album.addAsset",
|
||||||
|
"album.removeAsset",
|
||||||
|
"album.share",
|
||||||
|
"album.download",
|
||||||
|
"authDevice.delete",
|
||||||
|
"archive.read",
|
||||||
|
"face.create",
|
||||||
|
"face.read",
|
||||||
|
"face.update",
|
||||||
|
"face.delete",
|
||||||
|
"library.create",
|
||||||
|
"library.read",
|
||||||
|
"library.update",
|
||||||
|
"library.delete",
|
||||||
|
"library.statistics",
|
||||||
|
"timeline.read",
|
||||||
|
"timeline.download",
|
||||||
|
"memory.create",
|
||||||
|
"memory.read",
|
||||||
|
"memory.update",
|
||||||
|
"memory.delete",
|
||||||
|
"partner.create",
|
||||||
|
"partner.read",
|
||||||
|
"partner.update",
|
||||||
|
"partner.delete",
|
||||||
|
"person.create",
|
||||||
|
"person.read",
|
||||||
|
"person.update",
|
||||||
|
"person.delete",
|
||||||
|
"person.statistics",
|
||||||
|
"person.merge",
|
||||||
|
"person.reassign",
|
||||||
|
"sharedLink.create",
|
||||||
|
"sharedLink.read",
|
||||||
|
"sharedLink.update",
|
||||||
|
"sharedLink.delete",
|
||||||
|
"systemConfig.read",
|
||||||
|
"systemConfig.update",
|
||||||
|
"systemMetadata.read",
|
||||||
|
"systemMetadata.update",
|
||||||
|
"tag.create",
|
||||||
|
"tag.read",
|
||||||
|
"tag.update",
|
||||||
|
"tag.delete",
|
||||||
|
"admin.user.create",
|
||||||
|
"admin.user.read",
|
||||||
|
"admin.user.update",
|
||||||
|
"admin.user.delete"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"PersonCreateDto": {
|
"PersonCreateDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"birthDate": {
|
"birthDate": {
|
||||||
|
@ -299,10 +299,12 @@ export type ApiKeyResponseDto = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
permissions: Permission[];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
export type ApiKeyCreateDto = {
|
export type ApiKeyCreateDto = {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
permissions: Permission[];
|
||||||
};
|
};
|
||||||
export type ApiKeyCreateResponseDto = {
|
export type ApiKeyCreateResponseDto = {
|
||||||
apiKey: ApiKeyResponseDto;
|
apiKey: ApiKeyResponseDto;
|
||||||
@ -3125,6 +3127,79 @@ export enum Error {
|
|||||||
NotFound = "not_found",
|
NotFound = "not_found",
|
||||||
Unknown = "unknown"
|
Unknown = "unknown"
|
||||||
}
|
}
|
||||||
|
export enum Permission {
|
||||||
|
All = "all",
|
||||||
|
ActivityCreate = "activity.create",
|
||||||
|
ActivityRead = "activity.read",
|
||||||
|
ActivityUpdate = "activity.update",
|
||||||
|
ActivityDelete = "activity.delete",
|
||||||
|
ActivityStatistics = "activity.statistics",
|
||||||
|
ApiKeyCreate = "apiKey.create",
|
||||||
|
ApiKeyRead = "apiKey.read",
|
||||||
|
ApiKeyUpdate = "apiKey.update",
|
||||||
|
ApiKeyDelete = "apiKey.delete",
|
||||||
|
AssetRead = "asset.read",
|
||||||
|
AssetUpdate = "asset.update",
|
||||||
|
AssetDelete = "asset.delete",
|
||||||
|
AssetRestore = "asset.restore",
|
||||||
|
AssetShare = "asset.share",
|
||||||
|
AssetView = "asset.view",
|
||||||
|
AssetDownload = "asset.download",
|
||||||
|
AssetUpload = "asset.upload",
|
||||||
|
AlbumCreate = "album.create",
|
||||||
|
AlbumRead = "album.read",
|
||||||
|
AlbumUpdate = "album.update",
|
||||||
|
AlbumDelete = "album.delete",
|
||||||
|
AlbumStatistics = "album.statistics",
|
||||||
|
AlbumAddAsset = "album.addAsset",
|
||||||
|
AlbumRemoveAsset = "album.removeAsset",
|
||||||
|
AlbumShare = "album.share",
|
||||||
|
AlbumDownload = "album.download",
|
||||||
|
AuthDeviceDelete = "authDevice.delete",
|
||||||
|
ArchiveRead = "archive.read",
|
||||||
|
FaceCreate = "face.create",
|
||||||
|
FaceRead = "face.read",
|
||||||
|
FaceUpdate = "face.update",
|
||||||
|
FaceDelete = "face.delete",
|
||||||
|
LibraryCreate = "library.create",
|
||||||
|
LibraryRead = "library.read",
|
||||||
|
LibraryUpdate = "library.update",
|
||||||
|
LibraryDelete = "library.delete",
|
||||||
|
LibraryStatistics = "library.statistics",
|
||||||
|
TimelineRead = "timeline.read",
|
||||||
|
TimelineDownload = "timeline.download",
|
||||||
|
MemoryCreate = "memory.create",
|
||||||
|
MemoryRead = "memory.read",
|
||||||
|
MemoryUpdate = "memory.update",
|
||||||
|
MemoryDelete = "memory.delete",
|
||||||
|
PartnerCreate = "partner.create",
|
||||||
|
PartnerRead = "partner.read",
|
||||||
|
PartnerUpdate = "partner.update",
|
||||||
|
PartnerDelete = "partner.delete",
|
||||||
|
PersonCreate = "person.create",
|
||||||
|
PersonRead = "person.read",
|
||||||
|
PersonUpdate = "person.update",
|
||||||
|
PersonDelete = "person.delete",
|
||||||
|
PersonStatistics = "person.statistics",
|
||||||
|
PersonMerge = "person.merge",
|
||||||
|
PersonReassign = "person.reassign",
|
||||||
|
SharedLinkCreate = "sharedLink.create",
|
||||||
|
SharedLinkRead = "sharedLink.read",
|
||||||
|
SharedLinkUpdate = "sharedLink.update",
|
||||||
|
SharedLinkDelete = "sharedLink.delete",
|
||||||
|
SystemConfigRead = "systemConfig.read",
|
||||||
|
SystemConfigUpdate = "systemConfig.update",
|
||||||
|
SystemMetadataRead = "systemMetadata.read",
|
||||||
|
SystemMetadataUpdate = "systemMetadata.update",
|
||||||
|
TagCreate = "tag.create",
|
||||||
|
TagRead = "tag.read",
|
||||||
|
TagUpdate = "tag.update",
|
||||||
|
TagDelete = "tag.delete",
|
||||||
|
AdminUserCreate = "admin.user.create",
|
||||||
|
AdminUserRead = "admin.user.read",
|
||||||
|
AdminUserUpdate = "admin.user.update",
|
||||||
|
AdminUserDelete = "admin.user.delete"
|
||||||
|
}
|
||||||
export enum AssetMediaStatus {
|
export enum AssetMediaStatus {
|
||||||
Created = "created",
|
Created = "created",
|
||||||
Replaced = "replaced",
|
Replaced = "replaced",
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
ActivityStatisticsResponseDto,
|
ActivityStatisticsResponseDto,
|
||||||
} from 'src/dtos/activity.dto';
|
} from 'src/dtos/activity.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { ActivityService } from 'src/services/activity.service';
|
import { ActivityService } from 'src/services/activity.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -19,19 +20,19 @@ export class ActivityController {
|
|||||||
constructor(private service: ActivityService) {}
|
constructor(private service: ActivityService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ACTIVITY_READ })
|
||||||
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
||||||
return this.service.getAll(auth, dto);
|
return this.service.getAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('statistics')
|
@Get('statistics')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ACTIVITY_STATISTICS })
|
||||||
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
||||||
return this.service.getStatistics(auth, dto);
|
return this.service.getStatistics(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ACTIVITY_CREATE })
|
||||||
async createActivity(
|
async createActivity(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Body() dto: ActivityCreateDto,
|
@Body() dto: ActivityCreateDto,
|
||||||
@ -46,7 +47,7 @@ export class ActivityController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ACTIVITY_DELETE })
|
||||||
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from 'src/dtos/album.dto';
|
} from 'src/dtos/album.dto';
|
||||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { AlbumService } from 'src/services/album.service';
|
import { AlbumService } from 'src/services/album.service';
|
||||||
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
|
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation';
|
||||||
@ -22,24 +23,24 @@ export class AlbumController {
|
|||||||
constructor(private service: AlbumService) {}
|
constructor(private service: AlbumService) {}
|
||||||
|
|
||||||
@Get('count')
|
@Get('count')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ALBUM_STATISTICS })
|
||||||
getAlbumCount(@Auth() auth: AuthDto): Promise<AlbumCountResponseDto> {
|
getAlbumCount(@Auth() auth: AuthDto): Promise<AlbumCountResponseDto> {
|
||||||
return this.service.getCount(auth);
|
return this.service.getCount(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ALBUM_READ })
|
||||||
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||||
return this.service.getAll(auth, query);
|
return this.service.getAll(auth, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ALBUM_CREATE })
|
||||||
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authenticated({ sharedLink: true })
|
@Authenticated({ permission: Permission.ALBUM_READ, sharedLink: true })
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
getAlbumInfo(
|
getAlbumInfo(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@ -50,7 +51,7 @@ export class AlbumController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ALBUM_UPDATE })
|
||||||
updateAlbumInfo(
|
updateAlbumInfo(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -60,7 +61,7 @@ export class AlbumController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ALBUM_DELETE })
|
||||||
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put }
|
|||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto';
|
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { APIKeyService } from 'src/services/api-key.service';
|
import { APIKeyService } from 'src/services/api-key.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -12,25 +13,25 @@ export class APIKeyController {
|
|||||||
constructor(private service: APIKeyService) {}
|
constructor(private service: APIKeyService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.API_KEY_CREATE })
|
||||||
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.API_KEY_READ })
|
||||||
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
||||||
return this.service.getAll(auth);
|
return this.service.getAll(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.API_KEY_READ })
|
||||||
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
||||||
return this.service.getById(auth, id);
|
return this.service.getById(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.API_KEY_UPDATE })
|
||||||
updateApiKey(
|
updateApiKey(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -41,7 +42,7 @@ export class APIKeyController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.API_KEY_DELETE })
|
||||||
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(auth, id);
|
return this.service.delete(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Body, Controller, Get, Param, Put, Query } from '@nestjs/common';
|
|||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { AssetFaceResponseDto, FaceDto, PersonResponseDto } from 'src/dtos/person.dto';
|
import { AssetFaceResponseDto, FaceDto, PersonResponseDto } from 'src/dtos/person.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { PersonService } from 'src/services/person.service';
|
import { PersonService } from 'src/services/person.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -12,13 +13,13 @@ export class FaceController {
|
|||||||
constructor(private service: PersonService) {}
|
constructor(private service: PersonService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.FACE_READ })
|
||||||
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||||
return this.service.getFacesById(auth, dto);
|
return this.service.getFacesById(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.FACE_UPDATE })
|
||||||
reassignFacesById(
|
reassignFacesById(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
ValidateLibraryDto,
|
ValidateLibraryDto,
|
||||||
ValidateLibraryResponseDto,
|
ValidateLibraryResponseDto,
|
||||||
} from 'src/dtos/library.dto';
|
} from 'src/dtos/library.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Authenticated } from 'src/middleware/auth.guard';
|
import { Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { LibraryService } from 'src/services/library.service';
|
import { LibraryService } from 'src/services/library.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -19,25 +20,25 @@ export class LibraryController {
|
|||||||
constructor(private service: LibraryService) {}
|
constructor(private service: LibraryService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
|
||||||
getAllLibraries(): Promise<LibraryResponseDto[]> {
|
getAllLibraries(): Promise<LibraryResponseDto[]> {
|
||||||
return this.service.getAll();
|
return this.service.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.LIBRARY_CREATE, admin: true })
|
||||||
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
||||||
return this.service.create(dto);
|
return this.service.create(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.LIBRARY_UPDATE, admin: true })
|
||||||
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||||
return this.service.update(id, dto);
|
return this.service.update(id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
|
||||||
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
|
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
|
||||||
return this.service.get(id);
|
return this.service.get(id);
|
||||||
}
|
}
|
||||||
@ -52,13 +53,13 @@ export class LibraryController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.LIBRARY_DELETE, admin: true })
|
||||||
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
|
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.delete(id);
|
return this.service.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/statistics')
|
@Get(':id/statistics')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.LIBRARY_STATISTICS, admin: true })
|
||||||
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
|
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
|
||||||
return this.service.getStatistics(id);
|
return this.service.getStatistics(id);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { ApiTags } from '@nestjs/swagger';
|
|||||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto } from 'src/dtos/memory.dto';
|
import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto } from 'src/dtos/memory.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { MemoryService } from 'src/services/memory.service';
|
import { MemoryService } from 'src/services/memory.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -13,25 +14,25 @@ export class MemoryController {
|
|||||||
constructor(private service: MemoryService) {}
|
constructor(private service: MemoryService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||||
searchMemories(@Auth() auth: AuthDto): Promise<MemoryResponseDto[]> {
|
searchMemories(@Auth() auth: AuthDto): Promise<MemoryResponseDto[]> {
|
||||||
return this.service.search(auth);
|
return this.service.search(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.MEMORY_CREATE })
|
||||||
createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
|
createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||||
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.MEMORY_UPDATE })
|
||||||
updateMemory(
|
updateMemory(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -42,7 +43,7 @@ export class MemoryController {
|
|||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.MEMORY_DELETE })
|
||||||
deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/
|
|||||||
import { ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto';
|
import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { PartnerDirection } from 'src/interfaces/partner.interface';
|
import { PartnerDirection } from 'src/interfaces/partner.interface';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { PartnerService } from 'src/services/partner.service';
|
import { PartnerService } from 'src/services/partner.service';
|
||||||
@ -14,20 +15,20 @@ export class PartnerController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true })
|
@ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true })
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PARTNER_READ })
|
||||||
// TODO: remove 'direction' and convert to full query dto
|
// TODO: remove 'direction' and convert to full query dto
|
||||||
getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
||||||
return this.service.search(auth, dto);
|
return this.service.search(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id')
|
@Post(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PARTNER_CREATE })
|
||||||
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
||||||
return this.service.create(auth, id);
|
return this.service.create(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PARTNER_UPDATE })
|
||||||
updatePartner(
|
updatePartner(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -37,7 +38,7 @@ export class PartnerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PARTNER_DELETE })
|
||||||
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
PersonStatisticsResponseDto,
|
PersonStatisticsResponseDto,
|
||||||
PersonUpdateDto,
|
PersonUpdateDto,
|
||||||
} from 'src/dtos/person.dto';
|
} from 'src/dtos/person.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
|
||||||
import { PersonService } from 'src/services/person.service';
|
import { PersonService } from 'src/services/person.service';
|
||||||
@ -31,31 +32,31 @@ export class PersonController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_READ })
|
||||||
getAllPeople(@Auth() auth: AuthDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
|
getAllPeople(@Auth() auth: AuthDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||||
return this.service.getAll(auth, withHidden);
|
return this.service.getAll(auth, withHidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_CREATE })
|
||||||
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
|
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_UPDATE })
|
||||||
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||||
return this.service.updateAll(auth, dto);
|
return this.service.updateAll(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_READ })
|
||||||
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
||||||
return this.service.getById(auth, id);
|
return this.service.getById(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_UPDATE })
|
||||||
updatePerson(
|
updatePerson(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -65,14 +66,14 @@ export class PersonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/statistics')
|
@Get(':id/statistics')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_STATISTICS })
|
||||||
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
|
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
|
||||||
return this.service.getStatistics(auth, id);
|
return this.service.getStatistics(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/thumbnail')
|
@Get(':id/thumbnail')
|
||||||
@FileResponse()
|
@FileResponse()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_READ })
|
||||||
async getPersonThumbnail(
|
async getPersonThumbnail(
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@Next() next: NextFunction,
|
@Next() next: NextFunction,
|
||||||
@ -90,7 +91,7 @@ export class PersonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id/reassign')
|
@Put(':id/reassign')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_REASSIGN })
|
||||||
reassignFaces(
|
reassignFaces(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -100,7 +101,7 @@ export class PersonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/merge')
|
@Post(':id/merge')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.PERSON_MERGE })
|
||||||
mergePerson(
|
mergePerson(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
SharedLinkPasswordDto,
|
SharedLinkPasswordDto,
|
||||||
SharedLinkResponseDto,
|
SharedLinkResponseDto,
|
||||||
} from 'src/dtos/shared-link.dto';
|
} from 'src/dtos/shared-link.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
|
||||||
import { LoginDetails } from 'src/services/auth.service';
|
import { LoginDetails } from 'src/services/auth.service';
|
||||||
import { SharedLinkService } from 'src/services/shared-link.service';
|
import { SharedLinkService } from 'src/services/shared-link.service';
|
||||||
@ -22,7 +23,7 @@ export class SharedLinkController {
|
|||||||
constructor(private service: SharedLinkService) {}
|
constructor(private service: SharedLinkService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
||||||
getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
||||||
return this.service.getAll(auth);
|
return this.service.getAll(auth);
|
||||||
}
|
}
|
||||||
@ -48,19 +49,19 @@ export class SharedLinkController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
||||||
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.SHARED_LINK_CREATE })
|
||||||
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
|
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.SHARED_LINK_UPDATE })
|
||||||
updateSharedLink(
|
updateSharedLink(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -70,7 +71,7 @@ export class SharedLinkController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.SHARED_LINK_DELETE })
|
||||||
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Body, Controller, Get, Put } from '@nestjs/common';
|
import { Body, Controller, Get, Put } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Authenticated } from 'src/middleware/auth.guard';
|
import { Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { SystemConfigService } from 'src/services/system-config.service';
|
import { SystemConfigService } from 'src/services/system-config.service';
|
||||||
|
|
||||||
@ -10,25 +11,25 @@ export class SystemConfigController {
|
|||||||
constructor(private service: SystemConfigService) {}
|
constructor(private service: SystemConfigService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||||
getConfig(): Promise<SystemConfigDto> {
|
getConfig(): Promise<SystemConfigDto> {
|
||||||
return this.service.getConfig();
|
return this.service.getConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('defaults')
|
@Get('defaults')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||||
getConfigDefaults(): SystemConfigDto {
|
getConfigDefaults(): SystemConfigDto {
|
||||||
return this.service.getDefaults();
|
return this.service.getDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_CONFIG_UPDATE, admin: true })
|
||||||
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||||
return this.service.updateConfig(dto);
|
return this.service.updateConfig(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('storage-template-options')
|
@Get('storage-template-options')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||||
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
||||||
return this.service.getStorageTemplateOptions();
|
return this.service.getStorageTemplateOptions();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Body, Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
import { Body, Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AdminOnboardingUpdateDto, ReverseGeocodingStateResponseDto } from 'src/dtos/system-metadata.dto';
|
import { AdminOnboardingUpdateDto, ReverseGeocodingStateResponseDto } from 'src/dtos/system-metadata.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Authenticated } from 'src/middleware/auth.guard';
|
import { Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { SystemMetadataService } from 'src/services/system-metadata.service';
|
import { SystemMetadataService } from 'src/services/system-metadata.service';
|
||||||
|
|
||||||
@ -10,20 +11,20 @@ export class SystemMetadataController {
|
|||||||
constructor(private service: SystemMetadataService) {}
|
constructor(private service: SystemMetadataService) {}
|
||||||
|
|
||||||
@Get('admin-onboarding')
|
@Get('admin-onboarding')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
||||||
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
|
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
|
||||||
return this.service.getAdminOnboarding();
|
return this.service.getAdminOnboarding();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('admin-onboarding')
|
@Post('admin-onboarding')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_METADATA_UPDATE, admin: true })
|
||||||
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
|
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
|
||||||
return this.service.updateAdminOnboarding(dto);
|
return this.service.updateAdminOnboarding(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('reverse-geocoding-state')
|
@Get('reverse-geocoding-state')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
||||||
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
||||||
return this.service.getReverseGeocodingState();
|
return this.service.getReverseGeocodingState();
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
|||||||
import { AssetIdsDto } from 'src/dtos/asset.dto';
|
import { AssetIdsDto } from 'src/dtos/asset.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { CreateTagDto, TagResponseDto, UpdateTagDto } from 'src/dtos/tag.dto';
|
import { CreateTagDto, TagResponseDto, UpdateTagDto } from 'src/dtos/tag.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { TagService } from 'src/services/tag.service';
|
import { TagService } from 'src/services/tag.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -15,31 +16,31 @@ export class TagController {
|
|||||||
constructor(private service: TagService) {}
|
constructor(private service: TagService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.TAG_CREATE })
|
||||||
createTag(@Auth() auth: AuthDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> {
|
createTag(@Auth() auth: AuthDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> {
|
||||||
return this.service.create(auth, dto);
|
return this.service.create(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.TAG_READ })
|
||||||
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
|
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
|
||||||
return this.service.getAll(auth);
|
return this.service.getAll(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.TAG_READ })
|
||||||
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
||||||
return this.service.getById(auth, id);
|
return this.service.getById(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.TAG_UPDATE })
|
||||||
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateTagDto): Promise<TagResponseDto> {
|
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateTagDto): Promise<TagResponseDto> {
|
||||||
return this.service.update(auth, id, dto);
|
return this.service.update(auth, id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.TAG_DELETE })
|
||||||
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||||
return this.service.remove(auth, id);
|
return this.service.remove(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
UserAdminSearchDto,
|
UserAdminSearchDto,
|
||||||
UserAdminUpdateDto,
|
UserAdminUpdateDto,
|
||||||
} from 'src/dtos/user.dto';
|
} from 'src/dtos/user.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { UserAdminService } from 'src/services/user-admin.service';
|
import { UserAdminService } from 'src/services/user-admin.service';
|
||||||
import { UUIDParamDto } from 'src/validation';
|
import { UUIDParamDto } from 'src/validation';
|
||||||
@ -19,25 +20,25 @@ export class UserAdminController {
|
|||||||
constructor(private service: UserAdminService) {}
|
constructor(private service: UserAdminService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||||
searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
|
searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
|
||||||
return this.service.search(auth, dto);
|
return this.service.search(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_CREATE, admin: true })
|
||||||
createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.create(createUserDto);
|
return this.service.create(createUserDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||||
getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.get(auth, id);
|
return this.service.get(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
|
||||||
updateUserAdmin(
|
updateUserAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -47,7 +48,7 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
||||||
deleteUserAdmin(
|
deleteUserAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -57,13 +58,13 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/preferences')
|
@Get(':id/preferences')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||||
getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserPreferencesResponseDto> {
|
getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserPreferencesResponseDto> {
|
||||||
return this.service.getPreferences(auth, id);
|
return this.service.getPreferences(auth, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id/preferences')
|
@Put(':id/preferences')
|
||||||
@Authenticated()
|
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
|
||||||
updateUserPreferencesAdmin(
|
updateUserPreferencesAdmin(
|
||||||
@Auth() auth: AuthDto,
|
@Auth() auth: AuthDto,
|
||||||
@Param() { id }: UUIDParamDto,
|
@Param() { id }: UUIDParamDto,
|
||||||
@ -73,7 +74,7 @@ export class UserAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/restore')
|
@Post(':id/restore')
|
||||||
@Authenticated({ admin: true })
|
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
||||||
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||||
return this.service.restore(auth, id);
|
return this.service.restore(auth, id);
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ export class AccessCore {
|
|||||||
return this.repository.memory.checkOwnerAccess(auth.user.id, ids);
|
return this.repository.memory.checkOwnerAccess(auth.user.id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
case Permission.MEMORY_WRITE: {
|
case Permission.MEMORY_UPDATE: {
|
||||||
return this.repository.memory.checkOwnerAccess(auth.user.id, ids);
|
return this.repository.memory.checkOwnerAccess(auth.user.id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ export class AccessCore {
|
|||||||
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
case Permission.PERSON_WRITE: {
|
case Permission.PERSON_UPDATE: {
|
||||||
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
return await this.repository.person.checkOwnerAccess(auth.user.id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { ArrayMinSize, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Optional } from 'src/validation';
|
import { Optional } from 'src/validation';
|
||||||
export class APIKeyCreateDto {
|
export class APIKeyCreateDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@Optional()
|
@Optional()
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
|
@IsEnum(Permission, { each: true })
|
||||||
|
@ApiProperty({ enum: Permission, enumName: 'Permission', isArray: true })
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
permissions!: Permission[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class APIKeyUpdateDto {
|
export class APIKeyUpdateDto {
|
||||||
@ -23,4 +30,6 @@ export class APIKeyResponseDto {
|
|||||||
name!: string;
|
name!: string;
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
@ApiProperty({ enum: Permission, enumName: 'Permission', isArray: true })
|
||||||
|
permissions!: Permission[];
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity('api_keys')
|
@Entity('api_keys')
|
||||||
@ -18,6 +19,9 @@ export class APIKeyEntity {
|
|||||||
@Column()
|
@Column()
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
|
||||||
|
@Column({ array: true, type: 'varchar' })
|
||||||
|
permissions!: Permission[];
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamptz' })
|
@CreateDateColumn({ type: 'timestamptz' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@ -32,8 +32,18 @@ export enum MemoryType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
|
ALL = 'all',
|
||||||
|
|
||||||
ACTIVITY_CREATE = 'activity.create',
|
ACTIVITY_CREATE = 'activity.create',
|
||||||
|
ACTIVITY_READ = 'activity.read',
|
||||||
|
ACTIVITY_UPDATE = 'activity.update',
|
||||||
ACTIVITY_DELETE = 'activity.delete',
|
ACTIVITY_DELETE = 'activity.delete',
|
||||||
|
ACTIVITY_STATISTICS = 'activity.statistics',
|
||||||
|
|
||||||
|
API_KEY_CREATE = 'apiKey.create',
|
||||||
|
API_KEY_READ = 'apiKey.read',
|
||||||
|
API_KEY_UPDATE = 'apiKey.update',
|
||||||
|
API_KEY_DELETE = 'apiKey.delete',
|
||||||
|
|
||||||
// ASSET_CREATE = 'asset.create',
|
// ASSET_CREATE = 'asset.create',
|
||||||
ASSET_READ = 'asset.read',
|
ASSET_READ = 'asset.read',
|
||||||
@ -45,10 +55,12 @@ export enum Permission {
|
|||||||
ASSET_DOWNLOAD = 'asset.download',
|
ASSET_DOWNLOAD = 'asset.download',
|
||||||
ASSET_UPLOAD = 'asset.upload',
|
ASSET_UPLOAD = 'asset.upload',
|
||||||
|
|
||||||
// ALBUM_CREATE = 'album.create',
|
ALBUM_CREATE = 'album.create',
|
||||||
ALBUM_READ = 'album.read',
|
ALBUM_READ = 'album.read',
|
||||||
ALBUM_UPDATE = 'album.update',
|
ALBUM_UPDATE = 'album.update',
|
||||||
ALBUM_DELETE = 'album.delete',
|
ALBUM_DELETE = 'album.delete',
|
||||||
|
ALBUM_STATISTICS = 'album.statistics',
|
||||||
|
|
||||||
ALBUM_ADD_ASSET = 'album.addAsset',
|
ALBUM_ADD_ASSET = 'album.addAsset',
|
||||||
ALBUM_REMOVE_ASSET = 'album.removeAsset',
|
ALBUM_REMOVE_ASSET = 'album.removeAsset',
|
||||||
ALBUM_SHARE = 'album.share',
|
ALBUM_SHARE = 'album.share',
|
||||||
@ -58,20 +70,58 @@ export enum Permission {
|
|||||||
|
|
||||||
ARCHIVE_READ = 'archive.read',
|
ARCHIVE_READ = 'archive.read',
|
||||||
|
|
||||||
|
FACE_CREATE = 'face.create',
|
||||||
|
FACE_READ = 'face.read',
|
||||||
|
FACE_UPDATE = 'face.update',
|
||||||
|
FACE_DELETE = 'face.delete',
|
||||||
|
|
||||||
|
LIBRARY_CREATE = 'library.create',
|
||||||
|
LIBRARY_READ = 'library.read',
|
||||||
|
LIBRARY_UPDATE = 'library.update',
|
||||||
|
LIBRARY_DELETE = 'library.delete',
|
||||||
|
LIBRARY_STATISTICS = 'library.statistics',
|
||||||
|
|
||||||
TIMELINE_READ = 'timeline.read',
|
TIMELINE_READ = 'timeline.read',
|
||||||
TIMELINE_DOWNLOAD = 'timeline.download',
|
TIMELINE_DOWNLOAD = 'timeline.download',
|
||||||
|
|
||||||
|
MEMORY_CREATE = 'memory.create',
|
||||||
MEMORY_READ = 'memory.read',
|
MEMORY_READ = 'memory.read',
|
||||||
MEMORY_WRITE = 'memory.write',
|
MEMORY_UPDATE = 'memory.update',
|
||||||
MEMORY_DELETE = 'memory.delete',
|
MEMORY_DELETE = 'memory.delete',
|
||||||
|
|
||||||
PERSON_READ = 'person.read',
|
PARTNER_CREATE = 'partner.create',
|
||||||
PERSON_WRITE = 'person.write',
|
PARTNER_READ = 'partner.read',
|
||||||
PERSON_MERGE = 'person.merge',
|
PARTNER_UPDATE = 'partner.update',
|
||||||
|
PARTNER_DELETE = 'partner.delete',
|
||||||
|
|
||||||
PERSON_CREATE = 'person.create',
|
PERSON_CREATE = 'person.create',
|
||||||
|
PERSON_READ = 'person.read',
|
||||||
|
PERSON_UPDATE = 'person.update',
|
||||||
|
PERSON_DELETE = 'person.delete',
|
||||||
|
PERSON_STATISTICS = 'person.statistics',
|
||||||
|
PERSON_MERGE = 'person.merge',
|
||||||
PERSON_REASSIGN = 'person.reassign',
|
PERSON_REASSIGN = 'person.reassign',
|
||||||
|
|
||||||
PARTNER_UPDATE = 'partner.update',
|
SHARED_LINK_CREATE = 'sharedLink.create',
|
||||||
|
SHARED_LINK_READ = 'sharedLink.read',
|
||||||
|
SHARED_LINK_UPDATE = 'sharedLink.update',
|
||||||
|
SHARED_LINK_DELETE = 'sharedLink.delete',
|
||||||
|
|
||||||
|
SYSTEM_CONFIG_READ = 'systemConfig.read',
|
||||||
|
SYSTEM_CONFIG_UPDATE = 'systemConfig.update',
|
||||||
|
|
||||||
|
SYSTEM_METADATA_READ = 'systemMetadata.read',
|
||||||
|
SYSTEM_METADATA_UPDATE = 'systemMetadata.update',
|
||||||
|
|
||||||
|
TAG_CREATE = 'tag.create',
|
||||||
|
TAG_READ = 'tag.read',
|
||||||
|
TAG_UPDATE = 'tag.update',
|
||||||
|
TAG_DELETE = 'tag.delete',
|
||||||
|
|
||||||
|
ADMIN_USER_CREATE = 'admin.user.create',
|
||||||
|
ADMIN_USER_READ = 'admin.user.read',
|
||||||
|
ADMIN_USER_UPDATE = 'admin.user.update',
|
||||||
|
ADMIN_USER_DELETE = 'admin.user.delete',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SharedLinkType {
|
export enum SharedLinkType {
|
||||||
|
@ -11,6 +11,7 @@ import { Reflector } from '@nestjs/core';
|
|||||||
import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { AuthDto, ImmichQuery } from 'src/dtos/auth.dto';
|
import { AuthDto, ImmichQuery } from 'src/dtos/auth.dto';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { AuthService, LoginDetails } from 'src/services/auth.service';
|
import { AuthService, LoginDetails } from 'src/services/auth.service';
|
||||||
import { UAParser } from 'ua-parser-js';
|
import { UAParser } from 'ua-parser-js';
|
||||||
@ -25,7 +26,7 @@ export enum Metadata {
|
|||||||
|
|
||||||
type AdminRoute = { admin?: true };
|
type AdminRoute = { admin?: true };
|
||||||
type SharedLinkRoute = { sharedLink?: true };
|
type SharedLinkRoute = { sharedLink?: true };
|
||||||
type AuthenticatedOptions = AdminRoute | SharedLinkRoute;
|
type AuthenticatedOptions = { permission?: Permission } & (AdminRoute | SharedLinkRoute);
|
||||||
|
|
||||||
export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator => {
|
export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator => {
|
||||||
const decorators: MethodDecorator[] = [
|
const decorators: MethodDecorator[] = [
|
||||||
@ -89,13 +90,17 @@ export class AuthGuard implements CanActivate {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { admin: adminRoute, sharedLink: sharedLinkRoute } = { sharedLink: false, admin: false, ...options };
|
const {
|
||||||
|
admin: adminRoute,
|
||||||
|
sharedLink: sharedLinkRoute,
|
||||||
|
permission,
|
||||||
|
} = { sharedLink: false, admin: false, ...options };
|
||||||
const request = context.switchToHttp().getRequest<AuthRequest>();
|
const request = context.switchToHttp().getRequest<AuthRequest>();
|
||||||
|
|
||||||
request.user = await this.authService.authenticate({
|
request.user = await this.authService.authenticate({
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
queryParams: request.query as Record<string, string>,
|
queryParams: request.query as Record<string, string>,
|
||||||
metadata: { adminRoute, sharedLinkRoute, uri: request.path },
|
metadata: { adminRoute, sharedLinkRoute, permission, uri: request.path },
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
14
server/src/migrations/1723719333525-AddApiKeyPermissions.ts
Normal file
14
server/src/migrations/1723719333525-AddApiKeyPermissions.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddApiKeyPermissions1723719333525 implements MigrationInterface {
|
||||||
|
name = 'AddApiKeyPermissions1723719333525';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "api_keys" ADD "permissions" character varying array NOT NULL DEFAULT '{all}'`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "api_keys" ALTER COLUMN "permissions" DROP DEFAULT`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "api_keys" DROP COLUMN "permissions"`);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ FROM
|
|||||||
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
||||||
"APIKeyEntity"."key" AS "APIKeyEntity_key",
|
"APIKeyEntity"."key" AS "APIKeyEntity_key",
|
||||||
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||||
|
"APIKeyEntity"."permissions" AS "APIKeyEntity_permissions",
|
||||||
"APIKeyEntity__APIKeyEntity_user"."id" AS "APIKeyEntity__APIKeyEntity_user_id",
|
"APIKeyEntity__APIKeyEntity_user"."id" AS "APIKeyEntity__APIKeyEntity_user_id",
|
||||||
"APIKeyEntity__APIKeyEntity_user"."name" AS "APIKeyEntity__APIKeyEntity_user_name",
|
"APIKeyEntity__APIKeyEntity_user"."name" AS "APIKeyEntity__APIKeyEntity_user_name",
|
||||||
"APIKeyEntity__APIKeyEntity_user"."isAdmin" AS "APIKeyEntity__APIKeyEntity_user_isAdmin",
|
"APIKeyEntity__APIKeyEntity_user"."isAdmin" AS "APIKeyEntity__APIKeyEntity_user_isAdmin",
|
||||||
@ -46,6 +47,7 @@ SELECT
|
|||||||
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
||||||
"APIKeyEntity"."name" AS "APIKeyEntity_name",
|
"APIKeyEntity"."name" AS "APIKeyEntity_name",
|
||||||
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||||
|
"APIKeyEntity"."permissions" AS "APIKeyEntity_permissions",
|
||||||
"APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt",
|
"APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt",
|
||||||
"APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt"
|
"APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt"
|
||||||
FROM
|
FROM
|
||||||
@ -63,6 +65,7 @@ SELECT
|
|||||||
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
||||||
"APIKeyEntity"."name" AS "APIKeyEntity_name",
|
"APIKeyEntity"."name" AS "APIKeyEntity_name",
|
||||||
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||||
|
"APIKeyEntity"."permissions" AS "APIKeyEntity_permissions",
|
||||||
"APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt",
|
"APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt",
|
||||||
"APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt"
|
"APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt"
|
||||||
FROM
|
FROM
|
||||||
|
@ -31,6 +31,7 @@ export class ApiKeyRepository implements IKeyRepository {
|
|||||||
id: true,
|
id: true,
|
||||||
key: true,
|
key: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
|
permissions: true,
|
||||||
},
|
},
|
||||||
where: { key: hashedToken },
|
where: { key: hashedToken },
|
||||||
relations: {
|
relations: {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { APIKeyService } from 'src/services/api-key.service';
|
import { APIKeyService } from 'src/services/api-key.service';
|
||||||
@ -22,10 +23,11 @@ describe(APIKeyService.name, () => {
|
|||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should create a new key', async () => {
|
it('should create a new key', async () => {
|
||||||
keyMock.create.mockResolvedValue(keyStub.admin);
|
keyMock.create.mockResolvedValue(keyStub.admin);
|
||||||
await sut.create(authStub.admin, { name: 'Test Key' });
|
await sut.create(authStub.admin, { name: 'Test Key', permissions: [Permission.ALL] });
|
||||||
expect(keyMock.create).toHaveBeenCalledWith({
|
expect(keyMock.create).toHaveBeenCalledWith({
|
||||||
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||||
name: 'Test Key',
|
name: 'Test Key',
|
||||||
|
permissions: [Permission.ALL],
|
||||||
userId: authStub.admin.user.id,
|
userId: authStub.admin.user.id,
|
||||||
});
|
});
|
||||||
expect(cryptoMock.newPassword).toHaveBeenCalled();
|
expect(cryptoMock.newPassword).toHaveBeenCalled();
|
||||||
@ -35,11 +37,12 @@ describe(APIKeyService.name, () => {
|
|||||||
it('should not require a name', async () => {
|
it('should not require a name', async () => {
|
||||||
keyMock.create.mockResolvedValue(keyStub.admin);
|
keyMock.create.mockResolvedValue(keyStub.admin);
|
||||||
|
|
||||||
await sut.create(authStub.admin, {});
|
await sut.create(authStub.admin, { permissions: [Permission.ALL] });
|
||||||
|
|
||||||
expect(keyMock.create).toHaveBeenCalledWith({
|
expect(keyMock.create).toHaveBeenCalledWith({
|
||||||
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||||
name: 'API Key',
|
name: 'API Key',
|
||||||
|
permissions: [Permission.ALL],
|
||||||
userId: authStub.admin.user.id,
|
userId: authStub.admin.user.id,
|
||||||
});
|
});
|
||||||
expect(cryptoMock.newPassword).toHaveBeenCalled();
|
expect(cryptoMock.newPassword).toHaveBeenCalled();
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto } from 'src/dtos/api-key.dto';
|
import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, APIKeyUpdateDto } from 'src/dtos/api-key.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { APIKeyEntity } from 'src/entities/api-key.entity';
|
import { APIKeyEntity } from 'src/entities/api-key.entity';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
import { isGranted } from 'src/utils/access';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class APIKeyService {
|
export class APIKeyService {
|
||||||
@ -14,16 +15,22 @@ export class APIKeyService {
|
|||||||
|
|
||||||
async create(auth: AuthDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
async create(auth: AuthDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||||
const secret = this.crypto.newPassword(32);
|
const secret = this.crypto.newPassword(32);
|
||||||
|
|
||||||
|
if (auth.apiKey && !isGranted({ requested: dto.permissions, current: auth.apiKey.permissions })) {
|
||||||
|
throw new BadRequestException('Cannot grant permissions you do not have');
|
||||||
|
}
|
||||||
|
|
||||||
const entity = await this.repository.create({
|
const entity = await this.repository.create({
|
||||||
key: this.crypto.hashSha256(secret),
|
key: this.crypto.hashSha256(secret),
|
||||||
name: dto.name || 'API Key',
|
name: dto.name || 'API Key',
|
||||||
userId: auth.user.id,
|
userId: auth.user.id,
|
||||||
|
permissions: dto.permissions,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secret, apiKey: this.map(entity) };
|
return { secret, apiKey: this.map(entity) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(auth: AuthDto, id: string, dto: APIKeyCreateDto): Promise<APIKeyResponseDto> {
|
async update(auth: AuthDto, id: string, dto: APIKeyUpdateDto): Promise<APIKeyResponseDto> {
|
||||||
const exists = await this.repository.getById(auth.user.id, id);
|
const exists = await this.repository.getById(auth.user.id, id);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
throw new BadRequestException('API Key not found');
|
throw new BadRequestException('API Key not found');
|
||||||
@ -62,6 +69,7 @@ export class APIKeyService {
|
|||||||
name: entity.name,
|
name: entity.name,
|
||||||
createdAt: entity.createdAt,
|
createdAt: entity.createdAt,
|
||||||
updatedAt: entity.updatedAt,
|
updatedAt: entity.updatedAt,
|
||||||
|
permissions: entity.permissions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
} from 'src/dtos/auth.dto';
|
} from 'src/dtos/auth.dto';
|
||||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
|
import { Permission } from 'src/enum';
|
||||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
@ -38,6 +39,7 @@ import { ISessionRepository } from 'src/interfaces/session.interface';
|
|||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { isGranted } from 'src/utils/access';
|
||||||
import { HumanReadableSize } from 'src/utils/bytes';
|
import { HumanReadableSize } from 'src/utils/bytes';
|
||||||
|
|
||||||
export interface LoginDetails {
|
export interface LoginDetails {
|
||||||
@ -61,6 +63,7 @@ export type ValidateRequest = {
|
|||||||
metadata: {
|
metadata: {
|
||||||
sharedLinkRoute: boolean;
|
sharedLinkRoute: boolean;
|
||||||
adminRoute: boolean;
|
adminRoute: boolean;
|
||||||
|
permission?: Permission;
|
||||||
uri: string;
|
uri: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -157,7 +160,7 @@ export class AuthService {
|
|||||||
|
|
||||||
async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {
|
async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {
|
||||||
const authDto = await this.validate({ headers, queryParams });
|
const authDto = await this.validate({ headers, queryParams });
|
||||||
const { adminRoute, sharedLinkRoute, uri } = metadata;
|
const { adminRoute, sharedLinkRoute, permission, uri } = metadata;
|
||||||
|
|
||||||
if (!authDto.user.isAdmin && adminRoute) {
|
if (!authDto.user.isAdmin && adminRoute) {
|
||||||
this.logger.warn(`Denied access to admin only route: ${uri}`);
|
this.logger.warn(`Denied access to admin only route: ${uri}`);
|
||||||
@ -169,6 +172,10 @@ export class AuthService {
|
|||||||
throw new ForbiddenException('Forbidden');
|
throw new ForbiddenException('Forbidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authDto.apiKey && permission && !isGranted({ requested: [permission], current: authDto.apiKey.permissions })) {
|
||||||
|
throw new ForbiddenException(`Missing required permission: ${permission}`);
|
||||||
|
}
|
||||||
|
|
||||||
return authDto;
|
return authDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export class MemoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise<MemoryResponseDto> {
|
async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise<MemoryResponseDto> {
|
||||||
await this.access.requirePermission(auth, Permission.MEMORY_WRITE, id);
|
await this.access.requirePermission(auth, Permission.MEMORY_UPDATE, id);
|
||||||
|
|
||||||
const memory = await this.repository.update({
|
const memory = await this.repository.update({
|
||||||
id,
|
id,
|
||||||
@ -82,7 +82,7 @@ export class MemoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
async removeAssets(auth: AuthDto, id: string, dto: BulkIdsDto): Promise<BulkIdResponseDto[]> {
|
||||||
await this.access.requirePermission(auth, Permission.MEMORY_WRITE, id);
|
await this.access.requirePermission(auth, Permission.MEMORY_UPDATE, id);
|
||||||
|
|
||||||
const repos = { accessRepository: this.accessRepository, repository: this.repository };
|
const repos = { accessRepository: this.accessRepository, repository: this.repository };
|
||||||
const results = await removeAssets(auth, repos, {
|
const results = await removeAssets(auth, repos, {
|
||||||
|
@ -113,7 +113,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async reassignFaces(auth: AuthDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
|
async reassignFaces(auth: AuthDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
|
||||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, personId);
|
await this.access.requirePermission(auth, Permission.PERSON_UPDATE, personId);
|
||||||
const person = await this.findOrFail(personId);
|
const person = await this.findOrFail(personId);
|
||||||
const result: PersonResponseDto[] = [];
|
const result: PersonResponseDto[] = [];
|
||||||
const changeFeaturePhoto: string[] = [];
|
const changeFeaturePhoto: string[] = [];
|
||||||
@ -142,7 +142,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
|
async reassignFacesById(auth: AuthDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
|
||||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, personId);
|
await this.access.requirePermission(auth, Permission.PERSON_UPDATE, personId);
|
||||||
|
|
||||||
await this.access.requirePermission(auth, Permission.PERSON_CREATE, dto.id);
|
await this.access.requirePermission(auth, Permission.PERSON_CREATE, dto.id);
|
||||||
const face = await this.repository.getFaceById(dto.id);
|
const face = await this.repository.getFaceById(dto.id);
|
||||||
@ -226,7 +226,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
|
async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
|
||||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, id);
|
await this.access.requirePermission(auth, Permission.PERSON_UPDATE, id);
|
||||||
|
|
||||||
const { name, birthDate, isHidden, featureFaceAssetId: assetId } = dto;
|
const { name, birthDate, isHidden, featureFaceAssetId: assetId } = dto;
|
||||||
// TODO: set by faceId directly
|
// TODO: set by faceId directly
|
||||||
@ -581,7 +581,7 @@ export class PersonService {
|
|||||||
throw new BadRequestException('Cannot merge a person into themselves');
|
throw new BadRequestException('Cannot merge a person into themselves');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.access.requirePermission(auth, Permission.PERSON_WRITE, id);
|
await this.access.requirePermission(auth, Permission.PERSON_UPDATE, id);
|
||||||
let primaryPerson = await this.findOrFail(id);
|
let primaryPerson = await this.findOrFail(id);
|
||||||
const primaryName = primaryPerson.name || primaryPerson.id;
|
const primaryName = primaryPerson.name || primaryPerson.id;
|
||||||
|
|
||||||
|
15
server/src/utils/access.ts
Normal file
15
server/src/utils/access.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Permission } from 'src/enum';
|
||||||
|
import { setIsSuperset } from 'src/utils/set';
|
||||||
|
|
||||||
|
export type GrantedRequest = {
|
||||||
|
requested: Permission[];
|
||||||
|
current: Permission[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isGranted = ({ requested, current }: GrantedRequest) => {
|
||||||
|
if (current.includes(Permission.ALL)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return setIsSuperset(new Set(current), new Set(requested));
|
||||||
|
};
|
@ -1,25 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ApiKeyResponseDto } from '@immich/sdk';
|
|
||||||
import { mdiKeyVariant } from '@mdi/js';
|
import { mdiKeyVariant } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { t } from 'svelte-i18n';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
|
|
||||||
export let apiKey: Partial<ApiKeyResponseDto>;
|
export let apiKey: { name: string };
|
||||||
export let title: string;
|
export let title: string;
|
||||||
export let cancelText = $t('cancel');
|
export let cancelText = $t('cancel');
|
||||||
export let submitText = $t('save');
|
export let submitText = $t('save');
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
export let onSubmit: (apiKey: { name: string }) => void;
|
||||||
cancel: void;
|
export let onCancel: () => void;
|
||||||
submit: Partial<ApiKeyResponseDto>;
|
|
||||||
}>();
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (apiKey.name) {
|
if (apiKey.name) {
|
||||||
dispatch('submit', apiKey);
|
onSubmit({ name: apiKey.name });
|
||||||
} else {
|
} else {
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: $t('api_key_empty'),
|
message: $t('api_key_empty'),
|
||||||
@ -29,7 +25,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal {title} icon={mdiKeyVariant} onClose={handleCancel}>
|
<FullScreenModal {title} icon={mdiKeyVariant} onClose={() => onCancel()}>
|
||||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off" id="api-key-form">
|
<form on:submit|preventDefault={handleSubmit} autocomplete="off" id="api-key-form">
|
||||||
<div class="mb-4 flex flex-col gap-2">
|
<div class="mb-4 flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="name">{$t('name')}</label>
|
<label class="immich-form-label" for="name">{$t('name')}</label>
|
||||||
@ -37,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<svelte:fragment slot="sticky-bottom">
|
<svelte:fragment slot="sticky-bottom">
|
||||||
<Button color="gray" fullwidth on:click={handleCancel}>{cancelText}</Button>
|
<Button color="gray" fullwidth on:click={() => onCancel()}>{cancelText}</Button>
|
||||||
<Button type="submit" fullwidth form="api-key-form">{submitText}</Button>
|
<Button type="submit" fullwidth form="api-key-form">{submitText}</Button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</FullScreenModal>
|
</FullScreenModal>
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { createApiKey, deleteApiKey, getApiKeys, updateApiKey, type ApiKeyResponseDto } from '@immich/sdk';
|
import {
|
||||||
|
createApiKey,
|
||||||
|
deleteApiKey,
|
||||||
|
getApiKeys,
|
||||||
|
Permission,
|
||||||
|
updateApiKey,
|
||||||
|
type ApiKeyResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
|
import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
@ -14,7 +21,7 @@
|
|||||||
|
|
||||||
export let keys: ApiKeyResponseDto[];
|
export let keys: ApiKeyResponseDto[];
|
||||||
|
|
||||||
let newKey: Partial<ApiKeyResponseDto> | null = null;
|
let newKey: { name: string } | null = null;
|
||||||
let editKey: ApiKeyResponseDto | null = null;
|
let editKey: ApiKeyResponseDto | null = null;
|
||||||
let secret = '';
|
let secret = '';
|
||||||
|
|
||||||
@ -28,9 +35,14 @@
|
|||||||
keys = await getApiKeys();
|
keys = await getApiKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCreate = async (detail: Partial<ApiKeyResponseDto>) => {
|
const handleCreate = async ({ name }: { name: string }) => {
|
||||||
try {
|
try {
|
||||||
const data = await createApiKey({ apiKeyCreateDto: detail });
|
const data = await createApiKey({
|
||||||
|
apiKeyCreateDto: {
|
||||||
|
name,
|
||||||
|
permissions: [Permission.All],
|
||||||
|
},
|
||||||
|
});
|
||||||
secret = data.secret;
|
secret = data.secret;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_create_api_key'));
|
handleError(error, $t('errors.unable_to_create_api_key'));
|
||||||
@ -84,8 +96,8 @@
|
|||||||
title={$t('new_api_key')}
|
title={$t('new_api_key')}
|
||||||
submitText={$t('create')}
|
submitText={$t('create')}
|
||||||
apiKey={newKey}
|
apiKey={newKey}
|
||||||
on:submit={({ detail }) => handleCreate(detail)}
|
onSubmit={(key) => handleCreate(key)}
|
||||||
on:cancel={() => (newKey = null)}
|
onCancel={() => (newKey = null)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -98,8 +110,8 @@
|
|||||||
title={$t('api_key')}
|
title={$t('api_key')}
|
||||||
submitText={$t('save')}
|
submitText={$t('save')}
|
||||||
apiKey={editKey}
|
apiKey={editKey}
|
||||||
on:submit={({ detail }) => handleUpdate(detail)}
|
onSubmit={(key) => handleUpdate(key)}
|
||||||
on:cancel={() => (editKey = null)}
|
onCancel={() => (editKey = null)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user