mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-07-15 07:56:11 +08:00

Instead of having to parse this from the fullName. This way the code is easier to read and it's more reliable.
288 lines
7.7 KiB
Dart
288 lines
7.7 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:flutter/foundation.dart' as foundation;
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
import 'package:gitjournal/utils.dart';
|
|
import 'package:gitjournal/utils/logger.dart';
|
|
import 'githost.dart';
|
|
|
|
class GitLab implements GitHost {
|
|
static const _clientID =
|
|
"faf33c3716faf05bfb701b1b31e36c83a23c3ec2d7161f4ff00fba2275524d09";
|
|
|
|
var _platform = const MethodChannel('gitjournal.io/git');
|
|
var _accessCode = "";
|
|
var _stateOAuth = "";
|
|
|
|
@override
|
|
void init(OAuthCallback callback) {
|
|
Future _handleMessages(MethodCall call) async {
|
|
if (call.method != "onURL") {
|
|
Log.d("GitLab Unknown Call: " + call.method);
|
|
return;
|
|
}
|
|
|
|
closeWebView();
|
|
Log.d("GitLab: Called onUrl with " + call.arguments.toString());
|
|
|
|
String url = call.arguments["URL"];
|
|
var queryParamters = url.substring(url.indexOf('#') + 1);
|
|
var map = Uri.splitQueryString(queryParamters);
|
|
|
|
var state = map['state'];
|
|
if (state != _stateOAuth) {
|
|
Log.d("GitLab: OAuth State incorrect");
|
|
Log.d("Required State: " + _stateOAuth);
|
|
Log.d("Actual State: " + state);
|
|
callback(GitHostException.OAuthFailed);
|
|
return;
|
|
}
|
|
|
|
_accessCode = map['access_token'];
|
|
if (_accessCode == null) {
|
|
callback(GitHostException.OAuthFailed);
|
|
return;
|
|
}
|
|
|
|
callback(null);
|
|
}
|
|
|
|
_platform.setMethodCallHandler(_handleMessages);
|
|
Log.d("GitLab: Installed Handler");
|
|
}
|
|
|
|
@override
|
|
Future launchOAuthScreen() async {
|
|
_stateOAuth = _randomString(10);
|
|
|
|
var url =
|
|
"https://gitlab.com/oauth/authorize?client_id=$_clientID&response_type=token&state=$_stateOAuth&redirect_uri=gitjournal://login.oauth2";
|
|
return launch(url);
|
|
}
|
|
|
|
@override
|
|
Future<List<GitHostRepo>> listRepos() async {
|
|
if (_accessCode.isEmpty) {
|
|
throw GitHostException.MissingAccessCode;
|
|
}
|
|
|
|
// FIXME: pagination!
|
|
var url =
|
|
"https://gitlab.com/api/v4/projects?simple=true&membership=true&order_by=last_activity_at&access_token=$_accessCode";
|
|
|
|
if (foundation.kDebugMode) {
|
|
print(toCurlCommand(url, {}));
|
|
}
|
|
|
|
var response = await http.get(url);
|
|
if (response.statusCode != 200) {
|
|
Log.d("GitLab listRepos: Invalid response " +
|
|
response.statusCode.toString() +
|
|
": " +
|
|
response.body);
|
|
return null;
|
|
}
|
|
|
|
List<dynamic> list = jsonDecode(response.body);
|
|
var repos = <GitHostRepo>[];
|
|
list.forEach((dynamic d) {
|
|
var map = Map<String, dynamic>.from(d);
|
|
var repo = repoFromJson(map);
|
|
repos.add(repo);
|
|
});
|
|
|
|
// FIXME: Sort these based on some criteria
|
|
return repos;
|
|
}
|
|
|
|
@override
|
|
Future<GitHostRepo> createRepo(String name) async {
|
|
if (_accessCode.isEmpty) {
|
|
throw GitHostException.MissingAccessCode;
|
|
}
|
|
|
|
var url = "https://gitlab.com/api/v4/projects?access_token=$_accessCode";
|
|
var data = <String, dynamic>{
|
|
'name': name,
|
|
'visibility': 'private',
|
|
};
|
|
|
|
var headers = {
|
|
HttpHeaders.contentTypeHeader: "application/json",
|
|
};
|
|
|
|
var response =
|
|
await http.post(url, headers: headers, body: json.encode(data));
|
|
if (response.statusCode != 201) {
|
|
Log.d("GitLab createRepo: Invalid response " +
|
|
response.statusCode.toString() +
|
|
": " +
|
|
response.body);
|
|
|
|
if (response.statusCode == 400) {
|
|
if (response.body.contains("has already been taken")) {
|
|
throw GitHostException.RepoExists;
|
|
}
|
|
}
|
|
|
|
throw GitHostException.CreateRepoFailed;
|
|
}
|
|
|
|
Log.d("GitLab createRepo: " + response.body);
|
|
Map<String, dynamic> map = json.decode(response.body);
|
|
return repoFromJson(map);
|
|
}
|
|
|
|
@override
|
|
Future<GitHostRepo> getRepo(String name) async {
|
|
if (_accessCode.isEmpty) {
|
|
throw GitHostException.MissingAccessCode;
|
|
}
|
|
|
|
var userInfo = await getUserInfo();
|
|
var repo = userInfo.username + '%2F' + name;
|
|
var url =
|
|
"https://gitlab.com/api/v4/projects/$repo?access_token=$_accessCode";
|
|
|
|
var response = await http.get(url);
|
|
if (response.statusCode != 200) {
|
|
Log.d("GitLab getRepo: Invalid response " +
|
|
response.statusCode.toString() +
|
|
": " +
|
|
response.body);
|
|
|
|
throw GitHostException.GetRepoFailed;
|
|
}
|
|
|
|
Log.d("GitLab getRepo: " + response.body);
|
|
Map<String, dynamic> map = json.decode(response.body);
|
|
return repoFromJson(map);
|
|
}
|
|
|
|
@override
|
|
Future addDeployKey(String sshPublicKey, String repo) async {
|
|
if (_accessCode.isEmpty) {
|
|
throw GitHostException.MissingAccessCode;
|
|
}
|
|
|
|
repo = repo.replaceAll('/', '%2F');
|
|
var url =
|
|
"https://gitlab.com/api/v4/projects/$repo/deploy_keys?access_token=$_accessCode";
|
|
|
|
var data = {
|
|
'title': "GitJournal",
|
|
'key': sshPublicKey,
|
|
'can_push': true,
|
|
};
|
|
|
|
var headers = {
|
|
HttpHeaders.contentTypeHeader: "application/json",
|
|
};
|
|
|
|
var response =
|
|
await http.post(url, headers: headers, body: json.encode(data));
|
|
if (response.statusCode != 201) {
|
|
Log.d("GitLab addDeployKey: Invalid response " +
|
|
response.statusCode.toString() +
|
|
": " +
|
|
response.body);
|
|
throw GitHostException.DeployKeyFailed;
|
|
}
|
|
|
|
Log.d("GitLab addDeployKey: " + response.body);
|
|
return json.decode(response.body);
|
|
}
|
|
|
|
@visibleForTesting
|
|
GitHostRepo repoFromJson(Map<String, dynamic> parsedJson) {
|
|
DateTime updatedAt;
|
|
try {
|
|
updatedAt = DateTime.parse(parsedJson['last_activity_at'].toString());
|
|
} catch (e) {
|
|
Log.e(e);
|
|
}
|
|
var licenseMap = parsedJson['license'];
|
|
|
|
List<String> tags = [];
|
|
var tagList = parsedJson['tag_list'];
|
|
if (tagList is List) {
|
|
tags = tagList.map((e) => e.toString()).toList();
|
|
}
|
|
|
|
var fullName = parsedJson['path_with_namespace'].toString();
|
|
var namespace = parsedJson['namespace'];
|
|
var username = "";
|
|
if (namespace != null) {
|
|
username = (namespace as Map)["path"];
|
|
} else {
|
|
username = fullName.split('/').first;
|
|
}
|
|
|
|
return GitHostRepo(
|
|
name: parsedJson["name"],
|
|
username: username,
|
|
fullName: fullName,
|
|
cloneUrl: parsedJson['ssh_url_to_repo'],
|
|
updatedAt: updatedAt,
|
|
description: parsedJson['description'],
|
|
stars: parsedJson['star_count'],
|
|
forks: parsedJson['forks_count'],
|
|
issues: parsedJson['open_issues_count'],
|
|
language: parsedJson['language'],
|
|
private: parsedJson['visibility'] == 'private',
|
|
tags: tags,
|
|
license: licenseMap != null ? licenseMap['nickname'] : null,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<UserInfo> getUserInfo() async {
|
|
if (_accessCode.isEmpty) {
|
|
throw GitHostException.MissingAccessCode;
|
|
}
|
|
|
|
var url = "https://gitlab.com/api/v4/user?access_token=$_accessCode";
|
|
|
|
var response = await http.get(url);
|
|
if (response.statusCode != 200) {
|
|
Log.d("GitLab getUserInfo: Invalid response " +
|
|
response.statusCode.toString() +
|
|
": " +
|
|
response.body);
|
|
return null;
|
|
}
|
|
|
|
Map<String, dynamic> map = jsonDecode(response.body);
|
|
if (map == null || map.isEmpty) {
|
|
Log.d("GitLab getUserInfo: jsonDecode Failed " +
|
|
response.statusCode.toString() +
|
|
": " +
|
|
response.body);
|
|
return null;
|
|
}
|
|
|
|
return UserInfo(
|
|
name: map['name'],
|
|
email: map['email'],
|
|
username: map['username'],
|
|
);
|
|
}
|
|
}
|
|
|
|
String _randomString(int length) {
|
|
var rand = Random();
|
|
var codeUnits = List.generate(length, (index) {
|
|
return rand.nextInt(33) + 89;
|
|
});
|
|
|
|
return String.fromCharCodes(codeUnits);
|
|
}
|