340 lines
9.0 KiB
JavaScript
340 lines
9.0 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 tokenBody;
|
|
if (!silent) {
|
|
// Get an OAuth2 code
|
|
const { code } = await networkSvc.startOauth2(
|
|
`${serverUrl}/login/oauth/authorize`,
|
|
{
|
|
client_id: applicationId,
|
|
response_type: 'code',
|
|
redirect_uri: constants.oauth2RedirectUri,
|
|
},
|
|
silent,
|
|
);
|
|
// Exchange code with token
|
|
tokenBody = (await networkSvc.request({
|
|
method: 'POST',
|
|
url: `${serverUrl}/login/oauth/access_token`,
|
|
body: {
|
|
client_id: applicationId,
|
|
client_secret: applicationSecret,
|
|
code,
|
|
grant_type: 'authorization_code',
|
|
redirect_uri: constants.oauth2RedirectUri,
|
|
},
|
|
})).body;
|
|
} else {
|
|
// Exchange refreshToken with token
|
|
tokenBody = (await networkSvc.request({
|
|
method: 'POST',
|
|
url: `${serverUrl}/login/oauth/access_token`,
|
|
body: {
|
|
client_id: applicationId,
|
|
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 }, {
|
|
url: 'user',
|
|
});
|
|
const uniqueSub = `${serverUrl}/${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,
|
|
applicationSecret,
|
|
imgStorages: oldToken && oldToken.imgStorages,
|
|
refreshToken: tokenBody.refresh_token,
|
|
expiresOn: Date.now() + (tokenBody.expires_in * 1000),
|
|
serverUrl,
|
|
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,
|
|
isFile,
|
|
commitMessage,
|
|
}) {
|
|
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: isFile ? await utils.encodeFiletoBase64(content) : 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,
|
|
}) {
|
|
const refreshedToken = await this.refreshToken(token);
|
|
const { sha, content } = await request(refreshedToken, {
|
|
url: `repos/${projectId}/contents/${encodeURIComponent(path)}`,
|
|
params: { ref: branch },
|
|
});
|
|
return {
|
|
sha,
|
|
data: utils.decodeBase64(content),
|
|
};
|
|
},
|
|
};
|