feat(quick-261009-dkv): Teilen mit Personen und Gruppen - Durchstich, Oberflaeche
- Teilen-Dialog (Suche mit Verzoegerung, Ergebnisliste als Combobox, Berechtigung je Zeile aendern, entfernen) auf dem Modul-Dialog, Fehler mit Meldung der Nextcloud - Menueeintrag Teilen (nur bei Buchstabe R), Freigabe-Kennzeichen in Liste und Raster (ausgehend als Knopf, eingehend als Markierung) - Web-Client, reine Hilfen (Berechtigungsauswahl, Passwort erzeugen), Fehlertexte und Meldungen de/en; sharingDisabled traegt scope=groups fuer den Gruppentext Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,18 @@ import {
|
||||
checkDownload,
|
||||
checkZip,
|
||||
createFolder,
|
||||
createShare,
|
||||
deleteEntry,
|
||||
deleteShare,
|
||||
downloadUrl,
|
||||
getSharePolicy,
|
||||
listFolder,
|
||||
listSharesForPath,
|
||||
moveEntry,
|
||||
NextcloudFilesRequestError,
|
||||
previewUrl,
|
||||
searchSharees,
|
||||
updateShare,
|
||||
ZIP_MAX_NAMES,
|
||||
zipFormFields,
|
||||
zipPostUrl,
|
||||
@@ -150,3 +156,74 @@ describe('nextcloud-files-api — Herunterladen', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('nextcloud-files-api — Teilen', () => {
|
||||
it('getSharePolicy: GET /shares/policy', async () => {
|
||||
const fetchMock = mockFetch(200, { enabled: true });
|
||||
await getSharePolicy();
|
||||
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toBe(`${API}/modules/nextcloud-files/shares/policy`);
|
||||
expect(init.method).toBe('GET');
|
||||
expect(init.credentials).toBe('include');
|
||||
});
|
||||
|
||||
it('listSharesForPath codiert den Pfad nur in der Query', async () => {
|
||||
const fetchMock = mockFetch(200, {
|
||||
path: '/Ärger 100%',
|
||||
shares: [],
|
||||
hidden: 0,
|
||||
truncated: false,
|
||||
});
|
||||
await listSharesForPath('/Ärger 100%');
|
||||
const [url] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toBe(`${API}/modules/nextcloud-files/shares/by-path?path=%2F%C3%84rger%20100%25`);
|
||||
});
|
||||
|
||||
it('searchSharees sendet Begriff und Art in der Query', async () => {
|
||||
const fetchMock = mockFetch(200, { sharees: [] });
|
||||
await searchSharees('ben', 'folder');
|
||||
const [url] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toBe(`${API}/modules/nextcloud-files/sharees?term=ben&itemType=folder`);
|
||||
});
|
||||
|
||||
it('createShare sendet genau die Eingabe als JSON (nie eine Maske)', async () => {
|
||||
const fetchMock = mockFetch(201, { id: '1' });
|
||||
await createShare({ path: '/P', kind: 'user', shareWith: 'ben', access: 'view' });
|
||||
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
|
||||
expect(url).toBe(`${API}/modules/nextcloud-files/shares`);
|
||||
expect(init.method).toBe('POST');
|
||||
expect(init.body).toBe('{"path":"/P","kind":"user","shareWith":"ben","access":"view"}');
|
||||
});
|
||||
|
||||
it('updateShare und deleteShare tragen die Kennung im Pfad', async () => {
|
||||
const put = mockFetch(200, { id: '17' });
|
||||
await updateShare('17', { access: 'edit' });
|
||||
const [putUrl, putInit] = put.mock.calls[0] as [string, RequestInit];
|
||||
expect(putUrl).toBe(`${API}/modules/nextcloud-files/shares/17`);
|
||||
expect(putInit.method).toBe('PUT');
|
||||
expect(putInit.body).toBe('{"access":"edit"}');
|
||||
|
||||
const del = mockFetch(200, { deleted: true });
|
||||
await deleteShare('17');
|
||||
const [delUrl, delInit] = del.mock.calls[0] as [string, RequestInit];
|
||||
expect(delUrl).toBe(`${API}/modules/nextcloud-files/shares/17`);
|
||||
expect(delInit.method).toBe('DELETE');
|
||||
expect(delInit.body).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Fehler der Freigaben tragen ncMessage in extra', async () => {
|
||||
mockFetch(422, {
|
||||
code: 'shareRejected',
|
||||
message: 'Nextcloud hat diese Freigabe abgelehnt.',
|
||||
ncMessage: 'Nein',
|
||||
});
|
||||
const err = await createShare({
|
||||
path: '/P',
|
||||
kind: 'user',
|
||||
shareWith: 'b',
|
||||
access: 'view',
|
||||
}).catch((e) => e);
|
||||
expect(err.code).toBe('shareRejected');
|
||||
expect(err.extra).toEqual({ ncMessage: 'Nein' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -209,6 +209,8 @@ export interface NcEntry {
|
||||
permissions: string;
|
||||
hasPreview: boolean;
|
||||
favorite: boolean;
|
||||
/** Freigabearten des Eintrags (0 Person, 1 Gruppe, 3 Link ...); leer = nicht geteilt. */
|
||||
shareTypes: number[];
|
||||
}
|
||||
|
||||
export interface NcQuota {
|
||||
@@ -315,3 +317,111 @@ export function checkZip(dir: string, names: readonly string[]): Promise<{ ok: t
|
||||
json: { dir, names, check: true },
|
||||
});
|
||||
}
|
||||
|
||||
// --- Teilen (quick-261009-dkv) -------------------------------------------------------------------
|
||||
|
||||
export type NcShareKind = 'user' | 'group' | 'link';
|
||||
/** `custom`: eine Berechtigung, die sich nicht auf Ansehen/Bearbeiten/Hochladen abbilden laesst. */
|
||||
export type NcShareAccess = 'view' | 'edit' | 'upload' | 'custom';
|
||||
|
||||
export interface NcShare {
|
||||
id: string;
|
||||
kind: NcShareKind;
|
||||
path: string;
|
||||
name: string;
|
||||
itemType: 'file' | 'folder';
|
||||
mime: string | null;
|
||||
itemWritable: boolean;
|
||||
permissions: number;
|
||||
access: NcShareAccess;
|
||||
shareWith: string | null;
|
||||
shareWithName: string | null;
|
||||
ownerId: string | null;
|
||||
ownerName: string | null;
|
||||
canEdit: boolean;
|
||||
canDelete: boolean;
|
||||
/** `YYYY-MM-DD` oder null. */
|
||||
expiration: string | null;
|
||||
label: string;
|
||||
/** Nur bei eigenen Links; Tessera ruft sie nie auf. */
|
||||
url: string | null;
|
||||
hasPassword: boolean;
|
||||
target: string;
|
||||
sharedAt: string | null;
|
||||
pending?: boolean;
|
||||
}
|
||||
|
||||
export interface NcSharee {
|
||||
kind: 'user' | 'group';
|
||||
id: string;
|
||||
label: string;
|
||||
detail: string | null;
|
||||
}
|
||||
|
||||
/** Freigaberegeln der Nextcloud fuer den angemeldeten Benutzer (frisch gelesen). */
|
||||
export interface NcSharePolicy {
|
||||
enabled: boolean;
|
||||
groupsEnabled: boolean;
|
||||
links: {
|
||||
enabled: boolean;
|
||||
passwordRequired: boolean;
|
||||
passwordSuggested: boolean;
|
||||
expiryDefaultDays: number | null;
|
||||
expiryEnforced: boolean;
|
||||
uploadAllowed: boolean;
|
||||
multipleLinks: boolean;
|
||||
};
|
||||
internalExpiry: { defaultDays: number | null; enforced: boolean };
|
||||
minSearchLength: number;
|
||||
passwordMinLength: number | null;
|
||||
}
|
||||
|
||||
export interface NcShareList {
|
||||
path: string;
|
||||
shares: NcShare[];
|
||||
/** Freigaben anderer Arten (E-Mail, Server ...), nur als Zahl. */
|
||||
hidden: number;
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export interface CreateShareInput {
|
||||
path: string;
|
||||
kind: 'user' | 'group';
|
||||
shareWith: string;
|
||||
access: 'view' | 'edit' | 'upload';
|
||||
}
|
||||
|
||||
export interface UpdateShareInput {
|
||||
access?: 'view' | 'edit' | 'upload';
|
||||
}
|
||||
|
||||
export function getSharePolicy(): Promise<NcSharePolicy> {
|
||||
return request<NcSharePolicy>('/shares/policy');
|
||||
}
|
||||
|
||||
/** Freigaben eines Eintrags; der Pfad geht nur in der Query. */
|
||||
export function listSharesForPath(path: string): Promise<NcShareList> {
|
||||
return request<NcShareList>(`/shares/by-path?path=${encodeURIComponent(path)}`);
|
||||
}
|
||||
|
||||
/** Personen und Gruppen, die die Nextcloud zum Suchbegriff kennt. */
|
||||
export function searchSharees(
|
||||
term: string,
|
||||
itemType: 'file' | 'folder',
|
||||
): Promise<{ sharees: NcSharee[] }> {
|
||||
return request<{ sharees: NcSharee[] }>(
|
||||
`/sharees?term=${encodeURIComponent(term)}&itemType=${encodeURIComponent(itemType)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function createShare(input: CreateShareInput): Promise<NcShare> {
|
||||
return request<NcShare>('/shares', { method: 'POST', json: input });
|
||||
}
|
||||
|
||||
export function updateShare(id: string, input: UpdateShareInput): Promise<NcShare> {
|
||||
return request<NcShare>(`/shares/${encodeURIComponent(id)}`, { method: 'PUT', json: input });
|
||||
}
|
||||
|
||||
export function deleteShare(id: string): Promise<{ deleted: true }> {
|
||||
return request<{ deleted: true }>(`/shares/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user