Merge ef27331a93 into 33cdea88aa
commit
78df3530f5
|
|
@ -132,6 +132,8 @@ describe('/server', () => {
|
|||
oauthButtonText: 'Login with OAuth',
|
||||
trashDays: 30,
|
||||
userDeleteDelay: 7,
|
||||
sessionDeleteDelayBrowser: 90,
|
||||
sessionDeleteDelayMobile: 90,
|
||||
isInitialized: true,
|
||||
externalDomain: '',
|
||||
publicUsers: true,
|
||||
|
|
|
|||
|
|
@ -299,7 +299,10 @@
|
|||
"server_stats_page_description": "Admin server statistics page",
|
||||
"server_welcome_message": "Welcome message",
|
||||
"server_welcome_message_description": "A message that is displayed on the login page.",
|
||||
"settings_page_description": "Admin settings page",
|
||||
"session_delete_delay_browser_settings": "Session delete delay (browser)",
|
||||
"session_delete_delay_browser_settings_description": "Number of days after last update to delete browser sessions. Sessions will be deleted if not updated within this period.",
|
||||
"session_delete_delay_mobile_settings": "Session delete delay (mobile)",
|
||||
"session_delete_delay_mobile_settings_description": "Number of days after last update to delete mobile sessions. Sessions will be deleted if not updated within this period.",
|
||||
"sidecar_job": "Sidecar metadata",
|
||||
"sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem",
|
||||
"slideshow_duration_description": "Number of seconds to display each image",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class ServerConfigDto {
|
|||
required this.mapLightStyleUrl,
|
||||
required this.oauthButtonText,
|
||||
required this.publicUsers,
|
||||
required this.sessionDeleteDelayBrowser,
|
||||
required this.sessionDeleteDelayMobile,
|
||||
required this.trashDays,
|
||||
required this.userDeleteDelay,
|
||||
});
|
||||
|
|
@ -44,6 +46,10 @@ class ServerConfigDto {
|
|||
|
||||
bool publicUsers;
|
||||
|
||||
int sessionDeleteDelayBrowser;
|
||||
|
||||
int sessionDeleteDelayMobile;
|
||||
|
||||
int trashDays;
|
||||
|
||||
int userDeleteDelay;
|
||||
|
|
@ -59,6 +65,8 @@ class ServerConfigDto {
|
|||
other.mapLightStyleUrl == mapLightStyleUrl &&
|
||||
other.oauthButtonText == oauthButtonText &&
|
||||
other.publicUsers == publicUsers &&
|
||||
other.sessionDeleteDelayBrowser == sessionDeleteDelayBrowser &&
|
||||
other.sessionDeleteDelayMobile == sessionDeleteDelayMobile &&
|
||||
other.trashDays == trashDays &&
|
||||
other.userDeleteDelay == userDeleteDelay;
|
||||
|
||||
|
|
@ -74,11 +82,13 @@ class ServerConfigDto {
|
|||
(mapLightStyleUrl.hashCode) +
|
||||
(oauthButtonText.hashCode) +
|
||||
(publicUsers.hashCode) +
|
||||
(sessionDeleteDelayBrowser.hashCode) +
|
||||
(sessionDeleteDelayMobile.hashCode) +
|
||||
(trashDays.hashCode) +
|
||||
(userDeleteDelay.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]';
|
||||
String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, sessionDeleteDelayBrowser=$sessionDeleteDelayBrowser, sessionDeleteDelayMobile=$sessionDeleteDelayMobile, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
|
@ -91,6 +101,8 @@ class ServerConfigDto {
|
|||
json[r'mapLightStyleUrl'] = this.mapLightStyleUrl;
|
||||
json[r'oauthButtonText'] = this.oauthButtonText;
|
||||
json[r'publicUsers'] = this.publicUsers;
|
||||
json[r'sessionDeleteDelayBrowser'] = this.sessionDeleteDelayBrowser;
|
||||
json[r'sessionDeleteDelayMobile'] = this.sessionDeleteDelayMobile;
|
||||
json[r'trashDays'] = this.trashDays;
|
||||
json[r'userDeleteDelay'] = this.userDeleteDelay;
|
||||
return json;
|
||||
|
|
@ -114,6 +126,8 @@ class ServerConfigDto {
|
|||
mapLightStyleUrl: mapValueOfType<String>(json, r'mapLightStyleUrl')!,
|
||||
oauthButtonText: mapValueOfType<String>(json, r'oauthButtonText')!,
|
||||
publicUsers: mapValueOfType<bool>(json, r'publicUsers')!,
|
||||
sessionDeleteDelayBrowser: mapValueOfType<int>(json, r'sessionDeleteDelayBrowser')!,
|
||||
sessionDeleteDelayMobile: mapValueOfType<int>(json, r'sessionDeleteDelayMobile')!,
|
||||
trashDays: mapValueOfType<int>(json, r'trashDays')!,
|
||||
userDeleteDelay: mapValueOfType<int>(json, r'userDeleteDelay')!,
|
||||
);
|
||||
|
|
@ -172,6 +186,8 @@ class ServerConfigDto {
|
|||
'mapLightStyleUrl',
|
||||
'oauthButtonText',
|
||||
'publicUsers',
|
||||
'sessionDeleteDelayBrowser',
|
||||
'sessionDeleteDelayMobile',
|
||||
'trashDays',
|
||||
'userDeleteDelay',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,26 +14,40 @@ class SystemConfigUserDto {
|
|||
/// Returns a new [SystemConfigUserDto] instance.
|
||||
SystemConfigUserDto({
|
||||
required this.deleteDelay,
|
||||
required this.sessionDeleteDelayBrowser,
|
||||
required this.sessionDeleteDelayMobile,
|
||||
});
|
||||
|
||||
/// Minimum value: 1
|
||||
int deleteDelay;
|
||||
|
||||
/// Minimum value: 1
|
||||
int sessionDeleteDelayBrowser;
|
||||
|
||||
/// Minimum value: 1
|
||||
int sessionDeleteDelayMobile;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigUserDto &&
|
||||
other.deleteDelay == deleteDelay;
|
||||
other.deleteDelay == deleteDelay &&
|
||||
other.sessionDeleteDelayBrowser == sessionDeleteDelayBrowser &&
|
||||
other.sessionDeleteDelayMobile == sessionDeleteDelayMobile;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(deleteDelay.hashCode);
|
||||
(deleteDelay.hashCode) +
|
||||
(sessionDeleteDelayBrowser.hashCode) +
|
||||
(sessionDeleteDelayMobile.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigUserDto[deleteDelay=$deleteDelay]';
|
||||
String toString() => 'SystemConfigUserDto[deleteDelay=$deleteDelay, sessionDeleteDelayBrowser=$sessionDeleteDelayBrowser, sessionDeleteDelayMobile=$sessionDeleteDelayMobile]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'deleteDelay'] = this.deleteDelay;
|
||||
json[r'sessionDeleteDelayBrowser'] = this.sessionDeleteDelayBrowser;
|
||||
json[r'sessionDeleteDelayMobile'] = this.sessionDeleteDelayMobile;
|
||||
return json;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +61,8 @@ class SystemConfigUserDto {
|
|||
|
||||
return SystemConfigUserDto(
|
||||
deleteDelay: mapValueOfType<int>(json, r'deleteDelay')!,
|
||||
sessionDeleteDelayBrowser: mapValueOfType<int>(json, r'sessionDeleteDelayBrowser')!,
|
||||
sessionDeleteDelayMobile: mapValueOfType<int>(json, r'sessionDeleteDelayMobile')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -95,6 +111,8 @@ class SystemConfigUserDto {
|
|||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'deleteDelay',
|
||||
'sessionDeleteDelayBrowser',
|
||||
'sessionDeleteDelayMobile',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19194,6 +19194,12 @@
|
|||
"publicUsers": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sessionDeleteDelayBrowser": {
|
||||
"type": "integer"
|
||||
},
|
||||
"sessionDeleteDelayMobile": {
|
||||
"type": "integer"
|
||||
},
|
||||
"trashDays": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -19211,6 +19217,8 @@
|
|||
"mapLightStyleUrl",
|
||||
"oauthButtonText",
|
||||
"publicUsers",
|
||||
"sessionDeleteDelayBrowser",
|
||||
"sessionDeleteDelayMobile",
|
||||
"trashDays",
|
||||
"userDeleteDelay"
|
||||
],
|
||||
|
|
@ -22111,10 +22119,20 @@
|
|||
"deleteDelay": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"sessionDeleteDelayBrowser": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"sessionDeleteDelayMobile": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"deleteDelay"
|
||||
"deleteDelay",
|
||||
"sessionDeleteDelayBrowser",
|
||||
"sessionDeleteDelayMobile"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1215,6 +1215,8 @@ export type ServerConfigDto = {
|
|||
mapLightStyleUrl: string;
|
||||
oauthButtonText: string;
|
||||
publicUsers: boolean;
|
||||
sessionDeleteDelayBrowser: number;
|
||||
sessionDeleteDelayMobile: number;
|
||||
trashDays: number;
|
||||
userDeleteDelay: number;
|
||||
};
|
||||
|
|
@ -1601,6 +1603,8 @@ export type SystemConfigTrashDto = {
|
|||
};
|
||||
export type SystemConfigUserDto = {
|
||||
deleteDelay: number;
|
||||
sessionDeleteDelayBrowser: number;
|
||||
sessionDeleteDelayMobile: number;
|
||||
};
|
||||
export type SystemConfigDto = {
|
||||
backup: SystemConfigBackupsDto;
|
||||
|
|
|
|||
|
|
@ -186,6 +186,8 @@ export interface SystemConfig {
|
|||
};
|
||||
user: {
|
||||
deleteDelay: number;
|
||||
sessionDeleteDelayBrowser: number;
|
||||
sessionDeleteDelayMobile: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -388,5 +390,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||
},
|
||||
user: {
|
||||
deleteDelay: 7,
|
||||
sessionDeleteDelayBrowser: 90,
|
||||
sessionDeleteDelayMobile: 90,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ export class ServerConfigDto {
|
|||
trashDays!: number;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
userDeleteDelay!: number;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
sessionDeleteDelayBrowser!: number;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
sessionDeleteDelayMobile!: number;
|
||||
isInitialized!: boolean;
|
||||
isOnboarded!: boolean;
|
||||
externalDomain!: string;
|
||||
|
|
|
|||
|
|
@ -636,6 +636,18 @@ class SystemConfigUserDto {
|
|||
@Type(() => Number)
|
||||
@ApiProperty({ type: 'integer' })
|
||||
deleteDelay!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
@ApiProperty({ type: 'integer' })
|
||||
sessionDeleteDelayBrowser!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
@ApiProperty({ type: 'integer' })
|
||||
sessionDeleteDelayMobile!: number;
|
||||
}
|
||||
|
||||
export class SystemConfigDto implements SystemConfig {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ export class MaintenanceWorkerService {
|
|||
loginPageMessage: config.server.loginPageMessage,
|
||||
trashDays: config.trash.days,
|
||||
userDeleteDelay: config.user.deleteDelay,
|
||||
sessionDeleteDelayBrowser: config.user.sessionDeleteDelayBrowser,
|
||||
sessionDeleteDelayMobile: config.user.sessionDeleteDelayMobile,
|
||||
oauthButtonText: config.oauth.buttonText,
|
||||
isInitialized: true,
|
||||
isOnboarded: true,
|
||||
|
|
|
|||
|
|
@ -15,12 +15,22 @@ export type SessionSearchOptions = { updatedBefore: Date };
|
|||
export class SessionRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
cleanup() {
|
||||
cleanup(sessionDeleteDelayBrowser: number, sessionDeleteDelayMobile: number) {
|
||||
const mobileOsList = ['Android', 'iOS'];
|
||||
return this.db
|
||||
.deleteFrom('session')
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb('updatedAt', '<=', DateTime.now().minus({ days: 90 }).toJSDate()),
|
||||
eb.and([
|
||||
eb('deviceOS', 'in', mobileOsList),
|
||||
eb('updatedAt', '<=', DateTime.now().minus({ days: sessionDeleteDelayMobile }).toJSDate()),
|
||||
]),
|
||||
// Browser sessions
|
||||
eb.and([
|
||||
eb('deviceOS', 'not in', mobileOsList),
|
||||
eb('updatedAt', '<=', DateTime.now().minus({ days: sessionDeleteDelayBrowser }).toJSDate()),
|
||||
]),
|
||||
// Expired sessions
|
||||
eb.and([eb('expiresAt', 'is not', null), eb('expiresAt', '<=', DateTime.now().toJSDate())]),
|
||||
]),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,30 +8,30 @@ import { BaseService } from 'src/services/base.service';
|
|||
import { JobItem } from 'src/types';
|
||||
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
||||
|
||||
const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||
const asJobItem = (dto: JobCreateDto): JobItem[] => {
|
||||
switch (dto.name) {
|
||||
case ManualJobName.TagCleanup: {
|
||||
return { name: JobName.TagCleanup };
|
||||
return [{ name: JobName.TagCleanup }];
|
||||
}
|
||||
|
||||
case ManualJobName.PersonCleanup: {
|
||||
return { name: JobName.PersonCleanup };
|
||||
return [{ name: JobName.PersonCleanup }];
|
||||
}
|
||||
|
||||
case ManualJobName.UserCleanup: {
|
||||
return { name: JobName.UserDeleteCheck };
|
||||
return [{ name: JobName.UserDeleteCheck }, { name: JobName.SessionCleanup }];
|
||||
}
|
||||
|
||||
case ManualJobName.MemoryCleanup: {
|
||||
return { name: JobName.MemoryCleanup };
|
||||
return [{ name: JobName.MemoryCleanup }];
|
||||
}
|
||||
|
||||
case ManualJobName.MemoryCreate: {
|
||||
return { name: JobName.MemoryGenerate };
|
||||
return [{ name: JobName.MemoryGenerate }];
|
||||
}
|
||||
|
||||
case ManualJobName.BackupDatabase: {
|
||||
return { name: JobName.DatabaseBackup };
|
||||
return [{ name: JobName.DatabaseBackup }];
|
||||
}
|
||||
|
||||
default: {
|
||||
|
|
@ -43,7 +43,7 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
|||
@Injectable()
|
||||
export class JobService extends BaseService {
|
||||
async create(dto: JobCreateDto): Promise<void> {
|
||||
await this.jobRepository.queue(asJobItem(dto));
|
||||
await this.jobRepository.queueAll(asJobItem(dto));
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'JobRun' })
|
||||
|
|
|
|||
|
|
@ -160,6 +160,8 @@ describe(ServerService.name, () => {
|
|||
oauthButtonText: 'Login with OAuth',
|
||||
trashDays: 30,
|
||||
userDeleteDelay: 7,
|
||||
sessionDeleteDelayBrowser: 90,
|
||||
sessionDeleteDelayMobile: 90,
|
||||
isInitialized: undefined,
|
||||
isOnboarded: false,
|
||||
externalDomain: '',
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ export class ServerService extends BaseService {
|
|||
loginPageMessage: config.server.loginPageMessage,
|
||||
trashDays: config.trash.days,
|
||||
userDeleteDelay: config.user.deleteDelay,
|
||||
sessionDeleteDelayBrowser: config.user.sessionDeleteDelayBrowser,
|
||||
sessionDeleteDelayMobile: config.user.sessionDeleteDelayMobile,
|
||||
oauthButtonText: config.oauth.buttonText,
|
||||
isInitialized,
|
||||
isOnboarded: onboarding?.isOnboarded || false,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@ import { BaseService } from 'src/services/base.service';
|
|||
export class SessionService extends BaseService {
|
||||
@OnJob({ name: JobName.SessionCleanup, queue: QueueName.BackgroundTask })
|
||||
async handleCleanup(): Promise<JobStatus> {
|
||||
const sessions = await this.sessionRepository.cleanup();
|
||||
const config = await this.getConfig({ withCache: false });
|
||||
const sessions = await this.sessionRepository.cleanup(
|
||||
config.user.sessionDeleteDelayBrowser,
|
||||
config.user.sessionDeleteDelayMobile,
|
||||
);
|
||||
for (const session of sessions) {
|
||||
this.logger.verbose(`Deleted expired session token: ${session.deviceOS}/${session.deviceType}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ const partialConfig = {
|
|||
ffmpeg: { crf: 30 },
|
||||
oauth: { autoLaunch: true },
|
||||
trash: { days: 10 },
|
||||
user: { deleteDelay: 15 },
|
||||
user: {
|
||||
deleteDelay: 15,
|
||||
sessionDeleteDelayBrowser: 90,
|
||||
sessionDeleteDelayMobile: 90,
|
||||
},
|
||||
} satisfies DeepPartial<SystemConfig>;
|
||||
|
||||
const updatedConfig = Object.freeze<SystemConfig>({
|
||||
|
|
@ -197,6 +201,8 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
|||
},
|
||||
user: {
|
||||
deleteDelay: 15,
|
||||
sessionDeleteDelayBrowser: 90,
|
||||
sessionDeleteDelayMobile: 90,
|
||||
},
|
||||
notifications: {
|
||||
smtp: {
|
||||
|
|
@ -255,7 +261,11 @@ describe(SystemConfigService.name, () => {
|
|||
ffmpeg: { crf: 30 },
|
||||
oauth: { autoLaunch: true },
|
||||
trash: { days: 10 },
|
||||
user: { deleteDelay: 15 },
|
||||
user: {
|
||||
deleteDelay: 15,
|
||||
sessionDeleteDelayBrowser: 90,
|
||||
sessionDeleteDelayMobile: 90,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,22 @@
|
|||
bind:value={configToEdit.user.deleteDelay}
|
||||
isEdited={configToEdit.user.deleteDelay !== config.user.deleteDelay}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
min={1}
|
||||
label={$t('admin.session_delete_delay_browser_settings')}
|
||||
description={$t('admin.session_delete_delay_browser_settings_description')}
|
||||
bind:value={configToEdit.user.sessionDeleteDelayBrowser}
|
||||
isEdited={configToEdit.user.sessionDeleteDelayBrowser !== config.user.sessionDeleteDelayBrowser}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
min={1}
|
||||
label={$t('admin.session_delete_delay_mobile_settings')}
|
||||
description={$t('admin.session_delete_delay_mobile_settings_description')}
|
||||
bind:value={configToEdit.user.sessionDeleteDelayMobile}
|
||||
isEdited={configToEdit.user.sessionDeleteDelayMobile !== config.user.sessionDeleteDelayMobile}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ms-4">
|
||||
|
|
|
|||
Loading…
Reference in New Issue