diff --git a/docs/docs/administration/img/user-quota-size.webp b/docs/docs/administration/img/user-quota-size.webp index 9b5464ba3c5..2989bba392f 100644 Binary files a/docs/docs/administration/img/user-quota-size.webp and b/docs/docs/administration/img/user-quota-size.webp differ diff --git a/docs/docs/administration/user-management.mdx b/docs/docs/administration/user-management.mdx index 9375730f1ec..b98ffe0d694 100644 --- a/docs/docs/administration/user-management.mdx +++ b/docs/docs/administration/user-management.mdx @@ -31,7 +31,7 @@ Admin can send a welcome email if the Email option is set, you can learn here ho Admin can specify the storage quota for the user as the instance's admin; once the limit is reached, the user won't be able to upload to the instance anymore. -In order to select a storage quota, click on the pencil icon and enter the storage quota in GiB. You can choose an unlimited quota using the value 0 (default). +In order to select a storage quota, click on the pencil icon and enter the storage quota in GiB. You can choose an unlimited quota by leaving it empty (default). :::tip The system administrator can see the usage quota percentage of all users in Server Stats page. diff --git a/i18n/en.json b/i18n/en.json index de17cccebd3..454b776aac2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -164,7 +164,6 @@ "no_pattern_added": "No pattern added", "note_apply_storage_label_previous_assets": "Note: To apply the Storage Label to previously uploaded assets, run the", "note_cannot_be_changed_later": "NOTE: This cannot be changed later!", - "note_unlimited_quota": "Note: Enter 0 for unlimited quota", "notification_email_from_address": "From address", "notification_email_from_address_description": "Sender email address, for example: \"Immich Photo Server \"", "notification_email_host_description": "Host of the email server (e.g. smtp.immich.app)", @@ -929,7 +928,6 @@ "no_shared_albums_message": "Create an album to share photos and videos with people in your network", "not_in_any_album": "Not in any album", "note_apply_storage_label_to_previously_uploaded assets": "Note: To apply the Storage Label to previously uploaded assets, run the", - "note_unlimited_quota": "Note: Enter 0 for unlimited quota", "notes": "Notes", "notification_toggle_setting_description": "Enable email notifications", "notifications": "Notifications", @@ -1384,4 +1382,4 @@ "yes": "Yes", "you_dont_have_any_shared_links": "You don't have any shared links", "zoom_image": "Zoom Image" -} \ No newline at end of file +} diff --git a/mobile/openapi/lib/model/user_admin_create_dto.dart b/mobile/openapi/lib/model/user_admin_create_dto.dart index f2709be57b6..4bd12664263 100644 --- a/mobile/openapi/lib/model/user_admin_create_dto.dart +++ b/mobile/openapi/lib/model/user_admin_create_dto.dart @@ -36,7 +36,7 @@ class UserAdminCreateDto { String password; - /// Minimum value: 1 + /// Minimum value: 0 int? quotaSizeInBytes; /// diff --git a/mobile/openapi/lib/model/user_admin_update_dto.dart b/mobile/openapi/lib/model/user_admin_update_dto.dart index 6c6f73ae8e9..f0478c9b4c0 100644 --- a/mobile/openapi/lib/model/user_admin_update_dto.dart +++ b/mobile/openapi/lib/model/user_admin_update_dto.dart @@ -45,7 +45,7 @@ class UserAdminUpdateDto { /// String? password; - /// Minimum value: 1 + /// Minimum value: 0 int? quotaSizeInBytes; /// diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index ac30e9ae974..4e8e7ab8340 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -13624,7 +13624,7 @@ }, "quotaSizeInBytes": { "format": "int64", - "minimum": 1, + "minimum": 0, "nullable": true, "type": "integer" }, @@ -13763,7 +13763,7 @@ }, "quotaSizeInBytes": { "format": "int64", - "minimum": 1, + "minimum": 0, "nullable": true, "type": "integer" }, diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 0177e9b475b..afcd13f0e95 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; -import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator'; +import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; import { User, UserAdmin } from 'src/database'; import { UserMetadataEntity, UserMetadataItem } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; @@ -77,7 +77,7 @@ export class UserAdminCreateDto { @Optional({ nullable: true }) @IsNumber() - @IsPositive() + @Min(0) @ApiProperty({ type: 'integer', format: 'int64' }) quotaSizeInBytes?: number | null; @@ -115,7 +115,7 @@ export class UserAdminUpdateDto { @Optional({ nullable: true }) @IsNumber() - @IsPositive() + @Min(0) @ApiProperty({ type: 'integer', format: 'int64' }) quotaSizeInBytes?: number | null; } diff --git a/server/src/services/asset-media.service.ts b/server/src/services/asset-media.service.ts index 747d7e4514a..1b8a038b9ce 100644 --- a/server/src/services/asset-media.service.ts +++ b/server/src/services/asset-media.service.ts @@ -435,7 +435,7 @@ export class AssetMediaService extends BaseService { } private requireQuota(auth: AuthDto, size: number) { - if (auth.user.quotaSizeInBytes && auth.user.quotaSizeInBytes < auth.user.quotaUsageInBytes + size) { + if (auth.user.quotaSizeInBytes !== null && auth.user.quotaSizeInBytes < auth.user.quotaUsageInBytes + size) { throw new BadRequestException('Quota has been exceeded!'); } } diff --git a/web/src/lib/components/forms/create-user-form.svelte b/web/src/lib/components/forms/create-user-form.svelte index 87de0a90682..83b3154d4b7 100644 --- a/web/src/lib/components/forms/create-user-form.svelte +++ b/web/src/lib/components/forms/create-user-form.svelte @@ -30,7 +30,7 @@ let quotaSize: string | undefined = $state(); let isCreatingUser = $state(false); - let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null); + let quotaSizeInBytes = $derived(quotaSize === null ? null : convertToBytes(Number(quotaSize), ByteUnit.GiB)); let quotaSizeWarning = $derived( quotaSizeInBytes && userInteraction.serverInfo && quotaSizeInBytes > userInteraction.serverInfo.diskSizeRaw, ); @@ -113,7 +113,7 @@ - + {#if quotaSizeWarning} {$t('errors.quota_higher_than_disk_size')} {/if} diff --git a/web/src/lib/components/forms/edit-user-form.svelte b/web/src/lib/components/forms/edit-user-form.svelte index 2802ad3ab21..ab914e64309 100644 --- a/web/src/lib/components/forms/edit-user-form.svelte +++ b/web/src/lib/components/forms/edit-user-form.svelte @@ -28,7 +28,7 @@ onEditSuccess, }: Props = $props(); - let quotaSize = $state(user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, ByteUnit.GiB) : null); + let quotaSize = $state(user.quotaSizeInBytes === null ? null : convertFromBytes(user.quotaSizeInBytes, ByteUnit.GiB)); const previousQutoa = user.quotaSizeInBytes; @@ -48,7 +48,7 @@ email, name, storageLabel: storageLabel || '', - quotaSizeInBytes: quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null, + quotaSizeInBytes: quotaSize === null ? null : convertToBytes(Number(quotaSize), ByteUnit.GiB), }, }); @@ -126,8 +126,15 @@

{$t('errors.quota_higher_than_disk_size')}

{/if} - -

{$t('admin.note_unlimited_quota')}

+
diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte index 9ec2e9eab16..0ca17c4ed8d 100644 --- a/web/src/routes/admin/user-management/+page.svelte +++ b/web/src/routes/admin/user-management/+page.svelte @@ -209,7 +209,7 @@ {immichUser.name}
- {#if immichUser.quotaSizeInBytes && immichUser.quotaSizeInBytes > 0} + {#if immichUser.quotaSizeInBytes !== null && immichUser.quotaSizeInBytes >= 0} {getByteUnitString(immichUser.quotaSizeInBytes, $locale)} {:else}