Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit 6cb1098

Browse files
authored
feat: Gitea backend refactoring (#833)
1 parent 77f5a51 commit 6cb1098

File tree

9 files changed

+267
-147
lines changed

9 files changed

+267
-147
lines changed

packages/core/src/backends/gitea/API.ts

Lines changed: 67 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { Base64 } from 'js-base64';
2-
import initial from 'lodash/initial';
3-
import last from 'lodash/last';
4-
import partial from 'lodash/partial';
5-
import result from 'lodash/result';
6-
import trim from 'lodash/trim';
2+
import { trimStart, trim, result, partial, last, initial } from 'lodash';
73

84
import {
95
APIError,
@@ -22,12 +18,12 @@ import type { ApiRequest, FetchError } from '@staticcms/core/lib/util';
2218
import type AssetProxy from '@staticcms/core/valueObjects/AssetProxy';
2319
import type { Semaphore } from 'semaphore';
2420
import type {
21+
FilesResponse,
2522
GitGetBlobResponse,
2623
GitGetTreeResponse,
2724
GiteaUser,
2825
ReposGetResponse,
2926
ReposListCommitsResponse,
30-
ContentsResponse,
3127
} from './types';
3228

3329
export const API_NAME = 'Gitea';
@@ -40,6 +36,20 @@ export interface Config {
4036
originRepo?: string;
4137
}
4238

39+
enum FileOperation {
40+
CREATE = 'create',
41+
DELETE = 'delete',
42+
UPDATE = 'update',
43+
}
44+
45+
export interface ChangeFileOperation {
46+
content?: string;
47+
from_path?: string;
48+
path: string;
49+
operation: FileOperation;
50+
sha?: string;
51+
}
52+
4353
interface MetaDataObjects {
4454
entry: { path: string; sha: string };
4555
files: MediaFile[];
@@ -76,13 +86,6 @@ type MediaFile = {
7686
path: string;
7787
};
7888

79-
export type Diff = {
80-
path: string;
81-
newFile: boolean;
82-
sha: string;
83-
binary: boolean;
84-
};
85-
8689
export default class API {
8790
apiRoot: string;
8891
token: string;
@@ -120,7 +123,7 @@ export default class API {
120123

121124
static DEFAULT_COMMIT_MESSAGE = 'Automatically generated by Static CMS';
122125

123-
user(): Promise<{ full_name: string; login: string }> {
126+
user(): Promise<{ full_name: string; login: string; avatar_url: string }> {
124127
if (!this._userPromise) {
125128
this._userPromise = this.getUser();
126129
}
@@ -365,50 +368,53 @@ export default class API {
365368
async persistFiles(dataFiles: DataFile[], mediaFiles: AssetProxy[], options: PersistOptions) {
366369
// eslint-disable-next-line @typescript-eslint/no-explicit-any
367370
const files: (DataFile | AssetProxy)[] = mediaFiles.concat(dataFiles as any);
368-
for (const file of files) {
369-
const item: { raw?: string; sha?: string; toBase64?: () => Promise<string> } = file;
370-
const contentBase64 = await result(
371-
item,
372-
'toBase64',
373-
partial(this.toBase64, item.raw as string),
374-
);
375-
try {
376-
const oldSha = await this.getFileSha(file.path);
377-
await this.updateBlob(contentBase64, file, options, oldSha!);
378-
} catch {
379-
await this.createBlob(contentBase64, file, options);
380-
}
381-
}
371+
const operations = await this.getChangeFileOperations(files, this.branch);
372+
return this.changeFiles(operations, options);
382373
}
383374

384-
async updateBlob(
385-
contentBase64: string,
386-
file: AssetProxy | DataFile,
387-
options: PersistOptions,
388-
oldSha: string,
389-
) {
390-
await this.request(`${this.repoURL}/contents/${file.path}`, {
391-
method: 'PUT',
375+
async changeFiles(operations: ChangeFileOperation[], options: PersistOptions) {
376+
return (await this.request(`${this.repoURL}/contents`, {
377+
method: 'POST',
392378
body: JSON.stringify({
393379
branch: this.branch,
394-
content: contentBase64,
380+
files: operations,
395381
message: options.commitMessage,
396-
sha: oldSha,
397-
signoff: false,
398382
}),
399-
});
383+
})) as FilesResponse;
400384
}
401385

402-
async createBlob(contentBase64: string, file: AssetProxy | DataFile, options: PersistOptions) {
403-
await this.request(`${this.repoURL}/contents/${file.path}`, {
404-
method: 'POST',
405-
body: JSON.stringify({
406-
branch: this.branch,
407-
content: contentBase64,
408-
message: options.commitMessage,
409-
signoff: false,
386+
async getChangeFileOperations(files: { path: string; newPath?: string }[], branch: string) {
387+
const items: ChangeFileOperation[] = await Promise.all(
388+
files.map(async file => {
389+
const content = await result(
390+
file,
391+
'toBase64',
392+
partial(this.toBase64, (file as DataFile).raw),
393+
);
394+
let sha;
395+
let operation;
396+
let from_path;
397+
let path = trimStart(file.path, '/');
398+
try {
399+
sha = await this.getFileSha(file.path, { branch });
400+
operation = FileOperation.UPDATE;
401+
from_path = file.newPath && path;
402+
path = file.newPath ? trimStart(file.newPath, '/') : path;
403+
} catch {
404+
sha = undefined;
405+
operation = FileOperation.CREATE;
406+
}
407+
408+
return {
409+
operation,
410+
content,
411+
path,
412+
from_path,
413+
sha,
414+
} as ChangeFileOperation;
410415
}),
411-
});
416+
);
417+
return items;
412418
}
413419

414420
async getFileSha(path: string, { repoURL = this.repoURL, branch = this.branch } = {}) {
@@ -434,15 +440,18 @@ export default class API {
434440
}
435441

436442
async deleteFiles(paths: string[], message: string) {
437-
for (const file of paths) {
438-
const meta: ContentsResponse = await this.request(`${this.repoURL}/contents/${file}`, {
439-
method: 'GET',
440-
});
441-
await this.request(`${this.repoURL}/contents/${file}`, {
442-
method: 'DELETE',
443-
body: JSON.stringify({ branch: this.branch, message, sha: meta.sha, signoff: false }),
444-
});
445-
}
443+
const operations: ChangeFileOperation[] = await Promise.all(
444+
paths.map(async path => {
445+
const sha = await this.getFileSha(path);
446+
447+
return {
448+
operation: FileOperation.DELETE,
449+
path,
450+
sha,
451+
} as ChangeFileOperation;
452+
}),
453+
);
454+
return this.changeFiles(operations, { commitMessage: message });
446455
}
447456

448457
toBase64(str: string) {

packages/core/src/backends/gitea/AuthenticationPage.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,54 @@
11
import { Gitea as GiteaIcon } from '@styled-icons/simple-icons/Gitea';
2-
import React, { useCallback, useState } from 'react';
2+
import React, { useCallback, useMemo, useState } from 'react';
33

44
import Login from '@staticcms/core/components/login/Login';
5-
import { NetlifyAuthenticator } from '@staticcms/core/lib/auth';
5+
import { PkceAuthenticator } from '@staticcms/core/lib/auth';
66

77
import type { AuthenticationPageProps, TranslatedProps } from '@staticcms/core/interface';
88
import type { MouseEvent } from 'react';
99

1010
const GiteaAuthenticationPage = ({
1111
inProgress = false,
1212
config,
13-
base_url,
14-
siteId,
15-
authEndpoint,
13+
clearHash,
1614
onLogin,
1715
t,
1816
}: TranslatedProps<AuthenticationPageProps>) => {
1917
const [loginError, setLoginError] = useState<string | null>(null);
2018

19+
const auth = useMemo(() => {
20+
const { base_url = 'https://try.gitea.io', app_id = '' } = config.backend;
21+
22+
const clientSizeAuth = new PkceAuthenticator({
23+
base_url,
24+
auth_endpoint: 'login/oauth/authorize',
25+
app_id,
26+
auth_token_endpoint: 'login/oauth/access_token',
27+
clearHash,
28+
});
29+
30+
// Complete authentication if we were redirected back to from the provider.
31+
clientSizeAuth.completeAuth((err, data) => {
32+
if (err) {
33+
setLoginError(err.toString());
34+
} else if (data) {
35+
onLogin(data);
36+
}
37+
});
38+
return clientSizeAuth;
39+
}, [clearHash, config.backend, onLogin]);
40+
2141
const handleLogin = useCallback(
2242
(e: MouseEvent<HTMLButtonElement>) => {
2343
e.preventDefault();
24-
const cfg = {
25-
base_url,
26-
site_id: document.location.host.split(':')[0] === 'localhost' ? 'cms.netlify.com' : siteId,
27-
auth_endpoint: authEndpoint,
28-
};
29-
const auth = new NetlifyAuthenticator(cfg);
30-
31-
const { auth_scope: authScope = '' } = config.backend;
32-
33-
const scope = authScope || 'repo';
34-
auth.authenticate({ provider: 'gitea', scope }, (err, data) => {
44+
auth.authenticate({ scope: 'repository' }, err => {
3545
if (err) {
3646
setLoginError(err.toString());
37-
} else if (data) {
38-
onLogin(data);
47+
return;
3948
}
4049
});
4150
},
42-
[authEndpoint, base_url, config.backend, onLogin, siteId],
51+
[auth],
4352
);
4453

4554
return (

0 commit comments

Comments
 (0)