fix(nextcloud-files): WR-01 Adresspruefung folgt keiner Weiterleitung und meldet nur grob
- testAddress fragt status.php ueber ncRequest ab (keine Weiterleitung, Aufrufsperre, keine Zugangsdaten) statt ueber fetchNextcloudStatus mit bis zu drei Weiterleitungen - Ergebnis nur noch ok / maintenance / paused / locked / redirect / not-nextcloud / unreachable, ohne HTTP-Statuscode, Fehlerkuerzel oder Weiterleitungsziel - NEXTCLOUD_STATUS_FETCHER entfaellt; Specs mit Attrappen-Transport, Anleitung angepasst Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,49 +4,37 @@ vi.mock('../prisma/prisma-tenant.extension', () => ({
|
||||
forTenant: vi.fn((db: any, tenantId: string) => db.__bound(tenantId)),
|
||||
}));
|
||||
|
||||
import type { NextcloudCheckResult } from '../nextcloud-status/nextcloud-status-fetch';
|
||||
import { Readable } from 'node:stream';
|
||||
import { NextcloudCallGate } from './nextcloud-call-gate';
|
||||
import { NextcloudFilesSettingsService } from './nextcloud-files-settings.service';
|
||||
import type { NcTransportRequest, NcTransportResponse } from './nextcloud-http';
|
||||
|
||||
interface Row {
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
function reachable(over: Partial<NextcloudCheckResult> = {}): NextcloudCheckResult {
|
||||
return {
|
||||
reachable: true,
|
||||
maintenance: false,
|
||||
needsDbUpgrade: false,
|
||||
versionString: '34.0.4',
|
||||
edition: '',
|
||||
productName: 'Nextcloud',
|
||||
errorKind: null,
|
||||
errorDetail: null,
|
||||
...over,
|
||||
};
|
||||
const STATUS_OK = {
|
||||
installed: true,
|
||||
maintenance: false,
|
||||
needsDbUpgrade: false,
|
||||
version: '34.0.4.1',
|
||||
versionstring: '34.0.4',
|
||||
edition: '',
|
||||
productname: 'Nextcloud',
|
||||
};
|
||||
|
||||
function reply(
|
||||
statusCode: number,
|
||||
body: unknown = '',
|
||||
headers: Record<string, string> = {},
|
||||
): NcTransportResponse {
|
||||
const text = typeof body === 'string' ? body : JSON.stringify(body);
|
||||
return { statusCode, headers, body: Readable.from(text === '' ? [] : [Buffer.from(text)]) };
|
||||
}
|
||||
|
||||
function unreachable(
|
||||
errorKind: NextcloudCheckResult['errorKind'],
|
||||
errorDetail: string | null = null,
|
||||
): NextcloudCheckResult {
|
||||
return {
|
||||
reachable: false,
|
||||
maintenance: null,
|
||||
needsDbUpgrade: null,
|
||||
versionString: null,
|
||||
edition: null,
|
||||
productName: null,
|
||||
errorKind,
|
||||
errorDetail,
|
||||
};
|
||||
}
|
||||
type Handler = (req: NcTransportRequest) => NcTransportResponse;
|
||||
|
||||
function makeService(opts: {
|
||||
row?: Row | null;
|
||||
activeAccounts?: number;
|
||||
check?: NextcloudCheckResult;
|
||||
}) {
|
||||
function makeService(opts: { row?: Row | null; activeAccounts?: number; handler?: Handler }) {
|
||||
const state = { row: opts.row ?? null };
|
||||
const upsert = vi.fn(async ({ create, update }: any) => {
|
||||
state.row = state.row ? { ...state.row, ...update } : { ...create };
|
||||
@@ -64,10 +52,14 @@ function makeService(opts: {
|
||||
};
|
||||
},
|
||||
};
|
||||
const fetcher = vi.fn(async (..._a: unknown[]) => opts.check ?? reachable());
|
||||
const calls: NcTransportRequest[] = [];
|
||||
const transport = vi.fn(async (req: NcTransportRequest) => {
|
||||
calls.push(req);
|
||||
return (opts.handler ?? (() => reply(200, STATUS_OK)))(req);
|
||||
});
|
||||
const gate = new NextcloudCallGate();
|
||||
const service = new NextcloudFilesSettingsService(db as any, gate, fetcher as any);
|
||||
return { service, state, upsert, updateMany, count, fetcher, gate, tenants };
|
||||
const service = new NextcloudFilesSettingsService(db as any, gate, transport);
|
||||
return { service, state, upsert, updateMany, count, calls, transport, gate, tenants };
|
||||
}
|
||||
|
||||
const codeOf = async (p: Promise<unknown>) => {
|
||||
@@ -163,7 +155,7 @@ describe('NextcloudFilesSettingsService — Speichern', () => {
|
||||
});
|
||||
|
||||
it('mit confirmReconnect: Adresse gespeichert, alle Konten der Organisation abgelaufen, Zuhoerer gerufen', async () => {
|
||||
const { service, upsert, updateMany, fetcher } = makeService({
|
||||
const { service, upsert, updateMany, calls } = makeService({
|
||||
row: { baseUrl: 'https://alt.example' },
|
||||
activeAccounts: 2,
|
||||
});
|
||||
@@ -182,8 +174,8 @@ describe('NextcloudFilesSettingsService — Speichern', () => {
|
||||
});
|
||||
expect(listener).toHaveBeenCalledWith('t1');
|
||||
expect(other).toHaveBeenCalledWith('t1');
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
expect(fetcher.mock.calls[0][0]).toBe('https://neu.example');
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].url).toBe('https://neu.example/status.php');
|
||||
expect(view.check?.ok).toBe(true);
|
||||
expect(view.connectedAccounts).toBe(0);
|
||||
});
|
||||
@@ -195,9 +187,9 @@ describe('NextcloudFilesSettingsService — Speichern', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('NextcloudFilesSettingsService — Verbindungspruefung', () => {
|
||||
it('erreichbar: ok mit Version', async () => {
|
||||
const { service, fetcher } = makeService({ check: reachable() });
|
||||
describe('NextcloudFilesSettingsService — Verbindungspruefung (WR-01: grob, ohne Weiterleitung)', () => {
|
||||
it('erreichbar: ok mit Version, ein einziger GET auf status.php ohne Zugangsdaten', async () => {
|
||||
const { service, calls } = makeService({});
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res).toMatchObject({
|
||||
ok: true,
|
||||
@@ -206,26 +198,57 @@ describe('NextcloudFilesSettingsService — Verbindungspruefung', () => {
|
||||
productName: 'Nextcloud',
|
||||
});
|
||||
expect(res.message).toContain('34.0.4');
|
||||
expect(fetcher.mock.calls[0][0]).toBe('https://cloud.example');
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].method).toBe('GET');
|
||||
expect(calls[0].url).toBe('https://cloud.example/status.php');
|
||||
expect(calls[0].headers.authorization).toBeUndefined();
|
||||
expect(calls[0].headers.cookie).toBeUndefined();
|
||||
});
|
||||
|
||||
it('HTTP 400 ist der Hinweis auf die vertrauenswuerdigen Domains', async () => {
|
||||
const { service } = makeService({ check: unreachable('http-status', 'HTTP 400') });
|
||||
it('eine Weiterleitung wird NICHT befolgt; das Ergebnis nennt weder Ziel noch Status', async () => {
|
||||
const { service, calls } = makeService({
|
||||
handler: () => reply(302, '', { location: 'http://10.0.0.7:8080/secret' }),
|
||||
});
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res.ok).toBe(false);
|
||||
expect(res.kind).toBe('trusted-domains');
|
||||
expect(res.message).toContain('vertrauenswürdigen Domains');
|
||||
expect(res).toMatchObject({ ok: false, kind: 'redirect' });
|
||||
expect(calls).toHaveLength(1);
|
||||
const text = JSON.stringify(res);
|
||||
expect(text).not.toContain('10.0.0.7');
|
||||
expect(text).not.toContain('302');
|
||||
});
|
||||
|
||||
it('Zeitueberschreitung hat einen deutschen Text', async () => {
|
||||
const { service } = makeService({ check: unreachable('timeout') });
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res).toMatchObject({ ok: false, kind: 'timeout' });
|
||||
expect(res.message).toContain('nicht rechtzeitig');
|
||||
it('jeder andere HTTP-Fehler heisst nur "keine Nextcloud", ohne Statuscode', async () => {
|
||||
for (const status of [400, 401, 403, 404, 500, 502]) {
|
||||
const { service } = makeService({ handler: () => reply(status, 'interne Seite') });
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res.kind, String(status)).toBe('not-nextcloud');
|
||||
expect(JSON.stringify(res)).not.toContain(String(status));
|
||||
expect(JSON.stringify(res)).not.toContain('interne Seite');
|
||||
}
|
||||
});
|
||||
|
||||
it('keine Nextcloud-Antwort (200 ohne installed:true) ist not-nextcloud', async () => {
|
||||
const { service } = makeService({ handler: () => reply(200, '<html>Router</html>') });
|
||||
expect((await service.testAddress('https://cloud.example')).kind).toBe('not-nextcloud');
|
||||
});
|
||||
|
||||
it('Netzfehler, Zeitueberschreitung und Zertifikat sind gleichermassen "nicht erreichbar"', async () => {
|
||||
for (const code of ['ECONNREFUSED', 'UND_ERR_HEADERS_TIMEOUT', 'CERT_HAS_EXPIRED']) {
|
||||
const { service } = makeService({
|
||||
handler: () => {
|
||||
throw Object.assign(new Error('x'), { code });
|
||||
},
|
||||
});
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res, code).toMatchObject({ ok: false, kind: 'unreachable' });
|
||||
expect(JSON.stringify(res)).not.toContain(code);
|
||||
}
|
||||
});
|
||||
|
||||
it('Wartungsmodus ist kein Erfolg', async () => {
|
||||
const { service } = makeService({ check: reachable({ maintenance: true }) });
|
||||
const { service } = makeService({
|
||||
handler: () => reply(200, { ...STATUS_OK, maintenance: true }),
|
||||
});
|
||||
expect(await service.testAddress('https://cloud.example')).toMatchObject({
|
||||
ok: false,
|
||||
kind: 'maintenance',
|
||||
@@ -233,15 +256,15 @@ describe('NextcloudFilesSettingsService — Verbindungspruefung', () => {
|
||||
});
|
||||
|
||||
it('eine gesperrte Adresse (Aufrufsperre) fragt Nextcloud gar nicht erst', async () => {
|
||||
const { service, fetcher, gate } = makeService({});
|
||||
const { service, calls, gate } = makeService({});
|
||||
gate.pause('https://cloud.example');
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res).toMatchObject({ ok: false, kind: 'paused' });
|
||||
expect(fetcher).not.toHaveBeenCalled();
|
||||
expect(calls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('ein 429 aus der Pruefung sperrt den Ursprung fuer alle weiteren Aufrufe', async () => {
|
||||
const { service, gate } = makeService({ check: unreachable('http-status', 'HTTP 429') });
|
||||
const { service, gate } = makeService({ handler: () => reply(429) });
|
||||
const res = await service.testAddress('https://cloud.example');
|
||||
expect(res.kind).toBe('locked');
|
||||
expect(gate.isPaused('https://cloud.example').paused).toBe(true);
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { fetch as undiciFetch } from 'undici';
|
||||
import {
|
||||
normalizeCloudUrl,
|
||||
parseNextcloudStatus,
|
||||
} from '../nextcloud-status/nextcloud-status-fetch';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||
import {
|
||||
type FetchStatusOptions,
|
||||
fetchNextcloudStatus,
|
||||
type NextcloudCheckResult,
|
||||
normalizeCloudUrl,
|
||||
} from '../nextcloud-status/nextcloud-status-fetch';
|
||||
import { NextcloudCallGate } from './nextcloud-call-gate';
|
||||
import {
|
||||
type NextcloudFilesCheckView,
|
||||
@@ -15,14 +12,17 @@ import {
|
||||
type NextcloudFilesStatusView,
|
||||
ncErrorDefault,
|
||||
} from './nextcloud-files.types';
|
||||
import {
|
||||
discardBody,
|
||||
NEXTCLOUD_TRANSPORT,
|
||||
type NextcloudTransport,
|
||||
ncRequest,
|
||||
readCappedText,
|
||||
} from './nextcloud-http';
|
||||
|
||||
/** Nest-Token fuer den Abruf von `status.php` (Tests setzen eine Attrappe ein). */
|
||||
export const NEXTCLOUD_STATUS_FETCHER = 'NEXTCLOUD_STATUS_FETCHER';
|
||||
export type NextcloudStatusFetcher = (
|
||||
baseUrl: string,
|
||||
opts?: FetchStatusOptions,
|
||||
) => Promise<NextcloudCheckResult>;
|
||||
export const defaultStatusFetcher: NextcloudStatusFetcher = fetchNextcloudStatus;
|
||||
/** Zeitgrenze und Groessendeckel der Adresspruefung (`status.php`). */
|
||||
const CHECK_TIMEOUT_MS = 10_000;
|
||||
const CHECK_MAX_BYTES = 64 * 1024;
|
||||
|
||||
export type AddressChangeListener = (tenantId: string) => void;
|
||||
|
||||
@@ -30,8 +30,12 @@ interface ConfigRow {
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const TRUSTED_DOMAINS_TEXT =
|
||||
'Nextcloud lehnt diese Adresse ab. Bitte nehmen Sie den Rechnernamen in die vertrauenswürdigen Domains (trusted_domains) der Nextcloud auf.';
|
||||
const NOT_NEXTCLOUD_TEXT =
|
||||
'Unter dieser Adresse antwortet keine Nextcloud. Steht dort doch eine Nextcloud, nehmen Sie bitte den Rechnernamen in ihre vertrauenswürdigen Domains (trusted_domains) auf.';
|
||||
const UNREACHABLE_TEXT =
|
||||
'Unter dieser Adresse ist keine Nextcloud erreichbar. Bitte prüfen Sie die Adresse. Bei https muss der Tessera-Server dem Zertifikat vertrauen (eigene Zertifizierungsstelle siehe Betriebshandbuch).';
|
||||
const REDIRECT_TEXT =
|
||||
'Die Adresse leitet auf eine andere Adresse weiter. Tessera folgt keiner Weiterleitung; tragen Sie bitte die endgültige Adresse ein (zum Beispiel mit https).';
|
||||
|
||||
function hostOf(baseUrl: string): string | null {
|
||||
try {
|
||||
@@ -41,14 +45,6 @@ function hostOf(baseUrl: string): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
function originOf(baseUrl: string): string | null {
|
||||
try {
|
||||
return new URL(baseUrl).origin;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Einstellungen des Moduls "Nextcloud-Dateien" (quick-261008-mzu): die EINE
|
||||
* Nextcloud-Adresse der Organisation. Gesamter Zugriff auf `nextcloudFilesConfig`
|
||||
@@ -67,7 +63,7 @@ export class NextcloudFilesSettingsService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly gate: NextcloudCallGate,
|
||||
@Inject(NEXTCLOUD_STATUS_FETCHER) private readonly statusFetcher: NextcloudStatusFetcher,
|
||||
@Inject(NEXTCLOUD_TRANSPORT) private readonly transport: NextcloudTransport,
|
||||
) {}
|
||||
|
||||
/** Meldet einen Zuhoerer an, der nach einem Adresswechsel mit der Organisation aufgerufen wird. */
|
||||
@@ -149,48 +145,20 @@ export class NextcloudFilesSettingsService {
|
||||
// --- Verbindungspruefung -----------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fragt `status.php` der Adresse ab (ohne Zugangsdaten). Antwortet immer mit
|
||||
* `{ ok, kind, message, version, productName }`; Netz- und HTTP-Probleme sind
|
||||
* keine Serverfehler. Steht der Ursprung wegen eines 429 auf der Aufrufsperre,
|
||||
* geht gar keine Anfrage raus.
|
||||
* Fragt `status.php` der Adresse ab (ohne Zugangsdaten) — ueber `ncRequest`, also
|
||||
* mit denselben Regeln wie jeder andere Nextcloud-Aufruf: KEINE Weiterleitung wird
|
||||
* befolgt, und steht der Ursprung wegen eines 429 auf der Aufrufsperre, geht gar
|
||||
* keine Anfrage raus.
|
||||
*
|
||||
* Das Ergebnis ist bewusst GROB (WR-01): erreichbare Nextcloud (`ok`), Wartung,
|
||||
* gesperrt, Weiterleitung auf eine andere Adresse (`redirect`), keine Nextcloud
|
||||
* (`not-nextcloud`) oder nicht erreichbar (`unreachable`). Kein HTTP-Statuscode,
|
||||
* kein Fehlerkuerzel, kein Ziel einer Weiterleitung verlaesst den Server — die
|
||||
* Pruefung taugt so nicht zum Abtasten interner Adressen und Dienste.
|
||||
*/
|
||||
async testAddress(rawBaseUrl: string): Promise<NextcloudFilesCheckView> {
|
||||
const baseUrl = normalizeCloudUrl(rawBaseUrl);
|
||||
if (baseUrl === null) throw ncErrorDefault('invalidUrl');
|
||||
const origin = originOf(baseUrl);
|
||||
|
||||
if (origin) {
|
||||
const pause = this.gate.isPaused(origin);
|
||||
if (pause.paused) {
|
||||
const minutes = Math.max(1, Math.ceil(pause.retryAfterSeconds / 60));
|
||||
return {
|
||||
ok: false,
|
||||
kind: 'paused',
|
||||
message: `Nextcloud sperrt Anmeldungen vom Tessera-Server zurzeit. Bitte warten Sie etwa ${minutes} Minuten.`,
|
||||
version: null,
|
||||
productName: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Merkt, ob die Adresse nur ueber eine Weiterleitung antwortet: im Betrieb folgt Tessera
|
||||
// keiner Weiterleitung, die Adresse muss dann direkt eingetragen werden.
|
||||
let redirected = false;
|
||||
const recordingFetch = (async (...args: Parameters<typeof undiciFetch>) => {
|
||||
const response = await undiciFetch(...args);
|
||||
if (response.status >= 300 && response.status < 400) redirected = true;
|
||||
return response;
|
||||
}) as typeof undiciFetch;
|
||||
|
||||
const result = await this.statusFetcher(baseUrl, { fetchImpl: recordingFetch });
|
||||
return this.describeCheck(result, redirected, origin);
|
||||
}
|
||||
|
||||
private describeCheck(
|
||||
result: NextcloudCheckResult,
|
||||
redirected: boolean,
|
||||
origin: string | null,
|
||||
): NextcloudFilesCheckView {
|
||||
const fail = (kind: string, message: string): NextcloudFilesCheckView => ({
|
||||
ok: false,
|
||||
kind,
|
||||
@@ -199,64 +167,53 @@ export class NextcloudFilesSettingsService {
|
||||
productName: null,
|
||||
});
|
||||
|
||||
if (result.reachable) {
|
||||
if (result.maintenance) {
|
||||
return fail('maintenance', 'Nextcloud befindet sich im Wartungsmodus.');
|
||||
const res = await ncRequest(this.transport, this.gate, {
|
||||
baseUrl,
|
||||
prefix: '/status.php',
|
||||
method: 'GET',
|
||||
headers: { accept: 'application/json' },
|
||||
headersTimeoutMs: CHECK_TIMEOUT_MS,
|
||||
bodyTimeoutMs: CHECK_TIMEOUT_MS,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.kind === 'paused' || (res.kind === 'http' && res.status === 429)) {
|
||||
const minutes = Math.max(1, Math.ceil((res.retryAfterSeconds ?? 900) / 60));
|
||||
return fail(
|
||||
res.kind === 'paused' ? 'paused' : 'locked',
|
||||
`Nextcloud sperrt Anmeldungen vom Tessera-Server zurzeit. Bitte warten Sie etwa ${minutes} Minuten.`,
|
||||
);
|
||||
}
|
||||
const name = result.productName ?? 'Nextcloud';
|
||||
const version = result.versionString;
|
||||
const base = version
|
||||
? `Verbindung erfolgreich: ${name} ${version}.`
|
||||
: 'Verbindung erfolgreich.';
|
||||
const hint = redirected
|
||||
? ' Die Adresse leitet allerdings um. Tessera folgt keiner Weiterleitung, tragen Sie bitte die endgültige Adresse ein (zum Beispiel mit https).'
|
||||
: '';
|
||||
return {
|
||||
ok: true,
|
||||
kind: redirected ? 'redirected' : 'ok',
|
||||
message: base + hint,
|
||||
version,
|
||||
productName: result.productName,
|
||||
};
|
||||
if (res.kind === 'redirect') return fail('redirect', REDIRECT_TEXT);
|
||||
return fail('unreachable', UNREACHABLE_TEXT);
|
||||
}
|
||||
|
||||
switch (result.errorKind) {
|
||||
case 'timeout':
|
||||
return fail(
|
||||
'timeout',
|
||||
'Nextcloud hat nicht rechtzeitig geantwortet. Bitte prüfen Sie die Adresse und versuchen Sie es erneut.',
|
||||
);
|
||||
case 'network':
|
||||
return fail('network', 'Unter dieser Adresse ist keine Nextcloud erreichbar.');
|
||||
case 'tls':
|
||||
return fail(
|
||||
'tls',
|
||||
'Das Zertifikat der Nextcloud konnte nicht geprüft werden. Bitte prüfen Sie die Adresse oder hinterlegen Sie die Zertifizierungsstelle auf dem Tessera-Server.',
|
||||
);
|
||||
case 'http-status': {
|
||||
if (result.errorDetail === 'HTTP 400') return fail('trusted-domains', TRUSTED_DOMAINS_TEXT);
|
||||
if (result.errorDetail === 'HTTP 429') {
|
||||
if (origin) this.gate.pause(origin);
|
||||
return fail(
|
||||
'locked',
|
||||
'Nextcloud sperrt Anmeldungen vom Tessera-Server vorübergehend. Bitte versuchen Sie es in einigen Minuten erneut.',
|
||||
);
|
||||
}
|
||||
return fail(
|
||||
'http-status',
|
||||
`Nextcloud hat unter dieser Adresse mit einem Fehler geantwortet (${result.errorDetail ?? 'unbekannt'}).`,
|
||||
);
|
||||
}
|
||||
case 'redirect':
|
||||
return fail(
|
||||
'redirect',
|
||||
'Die Adresse leitet mehrfach um oder führt nicht zu einer Nextcloud. Bitte prüfen Sie sie.',
|
||||
);
|
||||
case 'too-large':
|
||||
case 'not-nextcloud':
|
||||
return fail('not-nextcloud', 'Unter dieser Adresse antwortet keine Nextcloud.');
|
||||
default:
|
||||
return fail('network', 'Unter dieser Adresse ist keine Nextcloud erreichbar.');
|
||||
if (res.status === 503) {
|
||||
discardBody(res.body);
|
||||
return fail('maintenance', 'Nextcloud befindet sich im Wartungsmodus.');
|
||||
}
|
||||
if (res.status < 200 || res.status >= 300) {
|
||||
discardBody(res.body);
|
||||
return fail('not-nextcloud', NOT_NEXTCLOUD_TEXT);
|
||||
}
|
||||
const text = await readCappedText(res.body, CHECK_MAX_BYTES);
|
||||
if (!text.ok) {
|
||||
return text.kind === 'too-large'
|
||||
? fail('not-nextcloud', NOT_NEXTCLOUD_TEXT)
|
||||
: fail('unreachable', UNREACHABLE_TEXT);
|
||||
}
|
||||
const parsed = parseNextcloudStatus(text.text);
|
||||
if (!parsed) return fail('not-nextcloud', NOT_NEXTCLOUD_TEXT);
|
||||
if (parsed.maintenance) return fail('maintenance', 'Nextcloud befindet sich im Wartungsmodus.');
|
||||
|
||||
const name = parsed.productName ?? 'Nextcloud';
|
||||
const version = parsed.versionString;
|
||||
return {
|
||||
ok: true,
|
||||
kind: 'ok',
|
||||
message: version ? `Verbindung erfolgreich: ${name} ${version}.` : 'Verbindung erfolgreich.',
|
||||
version,
|
||||
productName: parsed.productName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,7 @@ import { NextcloudFilesController } from './nextcloud-files.controller';
|
||||
import { seedNextcloudFilesModule } from './nextcloud-files.seed';
|
||||
import { NextcloudFilesService } from './nextcloud-files.service';
|
||||
import { NextcloudFilesAccountService } from './nextcloud-files-account.service';
|
||||
import {
|
||||
defaultStatusFetcher,
|
||||
NEXTCLOUD_STATUS_FETCHER,
|
||||
NextcloudFilesSettingsService,
|
||||
} from './nextcloud-files-settings.service';
|
||||
import { NextcloudFilesSettingsService } from './nextcloud-files-settings.service';
|
||||
import { NextcloudFilesTransferService } from './nextcloud-files-transfer.service';
|
||||
import { NEXTCLOUD_TRANSPORT, undiciTransport } from './nextcloud-http';
|
||||
import { LoginFlowStore, NextcloudLoginGuard } from './nextcloud-login-guard';
|
||||
@@ -36,7 +32,6 @@ import { NextcloudServerInfoService } from './nextcloud-server-info';
|
||||
LoginFlowStore,
|
||||
NextcloudCallGate,
|
||||
{ provide: NEXTCLOUD_TRANSPORT, useValue: undiciTransport },
|
||||
{ provide: NEXTCLOUD_STATUS_FETCHER, useValue: defaultStatusFetcher },
|
||||
],
|
||||
exports: [
|
||||
NextcloudCallGate,
|
||||
|
||||
Reference in New Issue
Block a user