Stackedit/src/services/providers/helpers/giteaHelper.js
2022-10-29 15:46:57 +08:00

380 lines
10 KiB
JavaScript

import utils from '../../utils';
import networkSvc from '../../networkSvc';
import store from '../../../store';
import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc';
import constants from '../../../data/constants';
const tokenExpirationMargin = 5 * 60 * 1000;
const request = ({ accessToken, serverUrl }, options) => networkSvc.request({
...options,
url: `${serverUrl}/api/v1/${options.url}`,
headers: {
...options.headers || {},
Authorization: `Bearer ${accessToken}`,
},
})
.then(res => res.body);
const getCommitMessage = (name, path) => {
const message = store.getters['data/computedSettings'].git[name];
return message.replace(/{{path}}/g, path);
};
/**
* https://try.gitea.io/api/swagger#/user/userGet
*/
const subPrefix = 'gt';
userSvc.setInfoResolver('gitea', subPrefix, async (sub) => {
try {
const [, serverUrl, username] = sub.match(/^(.+)\/([^/]+)$/);
const user = (await networkSvc.request({
url: `${serverUrl}/api/v1/users/${username}`,
})).body;
const uniqueSub = `${serverUrl}/${user.username}`;
return {
id: `${subPrefix}:${uniqueSub}`,
name: user.username,
imageUrl: user.avatar_url || '',
};
} catch (err) {
if (err.status !== 404) {
throw new Error('RETRY');
}
throw err;
}
});
export default {
subPrefix,
/**
* https://docs.gitea.io/en-us/oauth2-provider/
*/
async startOauth2(
serverUrl, applicationId, applicationSecret,
sub = null, silent = false, refreshToken,
) {
let apiUrl = serverUrl;
let clientId = applicationId;
let useServerConf = false;
// 获取gitea配置的参数
await networkSvc.getServerConf();
const confClientId = store.getters['data/serverConf'].giteaClientId;
const confServerUrl = store.getters['data/serverConf'].giteaUrl;
// 存在gitea配置则使用后端配置
if (confClientId && confServerUrl) {
apiUrl = confServerUrl;
clientId = confClientId;
useServerConf = true;
}
let tokenBody;
if (!silent) {
// Get an OAuth2 code
const { code } = await networkSvc.startOauth2(
`${apiUrl}/login/oauth/authorize`,
{
client_id: clientId,
response_type: 'code',
redirect_uri: constants.oauth2RedirectUri,
},
silent,
);
if (useServerConf) {
tokenBody = (await networkSvc.request({
method: 'GET',
url: 'oauth2/giteaToken',
params: {
code,
grant_type: 'authorization_code',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
} else {
// Exchange code with token
tokenBody = (await networkSvc.request({
method: 'POST',
url: `${apiUrl}/login/oauth/access_token`,
body: {
client_id: clientId,
client_secret: applicationSecret,
code,
grant_type: 'authorization_code',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
}
} else if (useServerConf) {
tokenBody = (await networkSvc.request({
method: 'GET',
url: 'oauth2/giteaToken',
params: {
refresh_token: refreshToken,
grant_type: 'refresh_token',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
} else {
// Exchange refreshToken with token
tokenBody = (await networkSvc.request({
method: 'POST',
url: `${apiUrl}/login/oauth/access_token`,
body: {
client_id: clientId,
client_secret: applicationSecret,
refresh_token: refreshToken,
grant_type: 'refresh_token',
redirect_uri: constants.oauth2RedirectUri,
},
})).body;
}
const accessToken = tokenBody.access_token;
// Call the user info endpoint
const user = await request({ accessToken, serverUrl: apiUrl }, {
url: 'user',
});
const uniqueSub = `${apiUrl}/${user.username}`;
userSvc.addUserInfo({
id: `${subPrefix}:${uniqueSub}`,
name: user.username,
imageUrl: user.avatar_url || '',
});
// Check the returned sub consistency
if (sub && uniqueSub !== sub) {
throw new Error('Gitea account ID not expected.');
}
const oldToken = store.getters['data/giteaTokensBySub'][uniqueSub];
// Build token object including scopes and sub
const token = {
accessToken,
name: user.username,
applicationId: clientId,
applicationSecret,
imgStorages: oldToken && oldToken.imgStorages,
refreshToken: tokenBody.refresh_token,
expiresOn: Date.now() + (tokenBody.expires_in * 1000),
serverUrl: apiUrl,
sub: uniqueSub,
};
// Add token to gitea tokens
store.dispatch('data/addGiteaToken', token);
return token;
},
// 刷新token
async refreshToken(token) {
const {
serverUrl,
applicationId,
applicationSecret,
sub,
} = token;
const lastToken = store.getters['data/giteaTokensBySub'][sub];
// 兼容旧的没有过期时间
if (!lastToken.expiresOn) {
await store.dispatch('modal/open', {
type: 'providerRedirection',
name: 'Gitea',
});
return this.startOauth2(serverUrl, applicationId, applicationSecret, sub);
}
// lastToken is not expired
if (lastToken.expiresOn > Date.now() + tokenExpirationMargin) {
return lastToken;
}
// existing token is about to expire.
// Try to get a new token in background
try {
return await this.startOauth2(
serverUrl, applicationId, applicationSecret,
sub, true, lastToken.refreshToken,
);
} catch (err) {
// If it fails try to popup a window
if (store.state.offline) {
throw err;
}
await store.dispatch('modal/open', {
type: 'providerRedirection',
name: 'Gitea',
});
return this.startOauth2(serverUrl, applicationId, applicationSecret, sub);
}
},
async addAccount({
serverUrl,
applicationId,
applicationSecret,
}, sub = null) {
const token = await this.startOauth2(
serverUrl,
applicationId,
applicationSecret,
sub,
);
badgeSvc.addBadge('addGiteaAccount');
return token;
},
async updateToken(token, imgStorageInfo) {
const imgStorages = token.imgStorages || [];
// 存储仓库唯一标识
const sid = utils.hash(`${imgStorageInfo.repoUri}${imgStorageInfo.path}${imgStorageInfo.branch}`);
// 查询是否存在 存在则更新
const filterStorages = imgStorages.filter(it => it.sid === sid);
if (filterStorages && filterStorages.length > 0) {
filterStorages.repoUri = imgStorageInfo.repoUri;
filterStorages.path = imgStorageInfo.path;
filterStorages.branch = imgStorageInfo.branch;
} else {
imgStorages.push({
sid,
repoUri: imgStorageInfo.repoUri,
path: imgStorageInfo.path,
branch: imgStorageInfo.branch,
});
token.imgStorages = imgStorages;
}
store.dispatch('data/addGiteaToken', token);
},
async removeTokenImgStorage(token, sid) {
if (!token.imgStorages || token.imgStorages.length === 0) {
return;
}
token.imgStorages = token.imgStorages.filter(it => it.sid !== sid);
store.dispatch('data/addGiteaToken', token);
},
async getProjectId(token, { projectPath, projectId }) {
if (projectId) {
return projectId;
}
const repoInfo = await this.getRepoInfo(token, projectPath);
return repoInfo.full_name;
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGet
*/
async getRepoInfo(token, projectPath) {
const [, repoFullName] = projectPath.match(/([^/]+\/[^/]+)$/);
const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, { url: `repos/${repoFullName}` });
},
/**
* https://try.gitea.io/api/swagger#/repository/GetTree
*/
async getTree({
token,
projectId,
branch,
}) {
const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, {
url: `repos/${projectId}/git/trees/${branch}`,
params: {
recursive: true,
per_page: 9999,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGetAllCommits
*/
async getCommits({
token,
projectId,
branch,
path,
}) {
const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, {
url: `repos/${projectId}/commits`,
params: {
sha: branch,
path,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoCreateFile
* https://try.gitea.io/api/swagger#/repository/repoUpdateFile
*/
async uploadFile({
token,
projectId,
branch,
path,
content,
sha,
isImg,
commitMessage,
}) {
let uploadContent = content;
if (isImg && typeof content !== 'string') {
uploadContent = await utils.encodeFiletoBase64(content);
}
const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, {
method: sha ? 'PUT' : 'POST',
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
body: {
message: commitMessage || getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
content: isImg ? uploadContent : utils.encodeBase64(content),
sha,
branch,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoDeleteFile
*/
async removeFile({
token,
projectId,
branch,
path,
sha,
}) {
const refreshedToken = await this.refreshToken(token);
return request(refreshedToken, {
method: 'DELETE',
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
body: {
message: getCommitMessage('deleteFileMessage', path),
sha,
branch,
},
});
},
/**
* https://try.gitea.io/api/swagger#/repository/repoGetContents
*/
async downloadFile({
token,
projectId,
branch,
path,
isImg,
}) {
const refreshedToken = await this.refreshToken(token);
const { sha, content } = await request(refreshedToken, {
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
params: { ref: branch },
});
return {
sha,
data: !isImg ? utils.decodeBase64(content) : content,
};
},
};