Skip to content

Commit d36b345

Browse files
authored
feat: extension store (#1301)
1 parent bc5c818 commit d36b345

12 files changed

Lines changed: 1783 additions & 417 deletions

File tree

ui/flutter/lib/api/api.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,10 @@ Future<void> putConfig(DownloaderConfig config) async {
200200
return _parse(() => _client.dio.put("api/v1/config", data: config), null);
201201
}
202202

203-
Future<void> installExtension(InstallExtension installExtension) async {
204-
return _parse(
203+
Future<String> installExtension(InstallExtension installExtension) async {
204+
return _parse<String>(
205205
() => _client.dio.post("api/v1/extensions", data: installExtension),
206-
null);
206+
(data) => data as String);
207207
}
208208

209209
Future<List<Extension>> getExtensions() async {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'dart:convert';
2+
3+
import 'package:dio/dio.dart';
4+
5+
import 'api.dart';
6+
import 'model/store_extension.dart';
7+
8+
class GopeedSiteApi {
9+
GopeedSiteApi._();
10+
11+
static final instance = GopeedSiteApi._();
12+
13+
static const _host = 'gopeed.com';
14+
15+
Future<Map<String, dynamic>> getRelease() async {
16+
final json = await _getJson('/api/release');
17+
return json as Map<String, dynamic>;
18+
}
19+
20+
Future<StoreExtensionPage> getExtensions({
21+
int page = 1,
22+
int limit = 20,
23+
StoreExtensionSort sort = StoreExtensionSort.stars,
24+
StoreSortOrder order = StoreSortOrder.desc,
25+
String? query,
26+
}) async {
27+
final json = await _getJson('/api/extensions', queryParameters: {
28+
'page': page.toString(),
29+
'limit': limit.clamp(1, 100).toString(),
30+
'sort': sort.name,
31+
'order': order.name,
32+
if (query != null && query.trim().isNotEmpty) 'q': query.trim(),
33+
});
34+
return StoreExtensionPage.fromJson(json as Map<String, dynamic>);
35+
}
36+
37+
Future<void> reportExtensionInstall(String id) async {
38+
final uri = Uri.https(_host, '/api/extensions/install');
39+
await proxyRequest(
40+
uri.toString(),
41+
data: {'id': id},
42+
options: Options(method: 'POST', contentType: Headers.jsonContentType),
43+
);
44+
}
45+
46+
Future<dynamic> _getJson(String path,
47+
{Map<String, String>? queryParameters}) async {
48+
final uri = Uri.https(_host, path, queryParameters);
49+
final Response<String> response = await proxyRequest(uri.toString());
50+
if (response.data == null || response.data!.isEmpty) {
51+
throw Exception('Empty response from $uri');
52+
}
53+
return jsonDecode(response.data!);
54+
}
55+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import 'dart:convert';
2+
3+
class StoreExtensionPage {
4+
final List<StoreExtension> data;
5+
final StorePagination pagination;
6+
7+
StoreExtensionPage({required this.data, required this.pagination});
8+
9+
factory StoreExtensionPage.fromJson(Map<String, dynamic> json) {
10+
return StoreExtensionPage(
11+
data: (json['data'] as List<dynamic>? ?? [])
12+
.map((e) => StoreExtension.fromJson(e as Map<String, dynamic>))
13+
.toList(),
14+
pagination: StorePagination.fromJson(
15+
json['pagination'] as Map<String, dynamic>? ?? {}),
16+
);
17+
}
18+
}
19+
20+
class StorePagination {
21+
final int page;
22+
final int limit;
23+
final int total;
24+
final int totalPages;
25+
final bool hasNext;
26+
final bool hasPrev;
27+
28+
StorePagination({
29+
required this.page,
30+
required this.limit,
31+
required this.total,
32+
required this.totalPages,
33+
required this.hasNext,
34+
required this.hasPrev,
35+
});
36+
37+
factory StorePagination.fromJson(Map<String, dynamic> json) {
38+
return StorePagination(
39+
page: (json['page'] as num?)?.toInt() ?? 1,
40+
limit: (json['limit'] as num?)?.toInt() ?? 20,
41+
total: (json['total'] as num?)?.toInt() ?? 0,
42+
totalPages: (json['totalPages'] as num?)?.toInt() ?? 1,
43+
hasNext: json['hasNext'] as bool? ?? false,
44+
hasPrev: json['hasPrev'] as bool? ?? false,
45+
);
46+
}
47+
}
48+
49+
class StoreExtension {
50+
final String id;
51+
final String repoFullName;
52+
final String repoUrl;
53+
final String? directory;
54+
final String? commitSha;
55+
final String name;
56+
final String author;
57+
final String title;
58+
final String description;
59+
final String? icon;
60+
final String version;
61+
final String? homepage;
62+
final String? readme;
63+
final int installCount;
64+
final int stars;
65+
final List<String> topics;
66+
final DateTime? createdAt;
67+
final DateTime? updatedAt;
68+
69+
StoreExtension({
70+
required this.id,
71+
required this.repoFullName,
72+
required this.repoUrl,
73+
this.directory,
74+
this.commitSha,
75+
required this.name,
76+
required this.author,
77+
required this.title,
78+
required this.description,
79+
this.icon,
80+
required this.version,
81+
this.homepage,
82+
this.readme,
83+
required this.installCount,
84+
required this.stars,
85+
required this.topics,
86+
this.createdAt,
87+
this.updatedAt,
88+
});
89+
90+
factory StoreExtension.fromJson(Map<String, dynamic> json) {
91+
return StoreExtension(
92+
id: json['id'] as String? ?? '',
93+
repoFullName: json['repoFullName'] as String? ?? '',
94+
repoUrl: json['repoUrl'] as String? ?? '',
95+
directory: json['directory'] as String?,
96+
commitSha: json['commitSha'] as String?,
97+
name: json['name'] as String? ?? '',
98+
author: json['author'] as String? ?? '',
99+
title: json['title'] as String? ?? '',
100+
description: json['description'] as String? ?? '',
101+
icon: json['icon'] as String?,
102+
version: json['version'] as String? ?? '0.0.0',
103+
homepage: json['homepage'] as String?,
104+
readme: json['readme'] as String?,
105+
installCount: (json['installCount'] as num?)?.toInt() ?? 0,
106+
stars: (json['stars'] as num?)?.toInt() ?? 0,
107+
topics: _parseTopics(json['topics']),
108+
createdAt: _parseDate(json['createdAt']),
109+
updatedAt: _parseDate(json['updatedAt']),
110+
);
111+
}
112+
113+
static List<String> _parseTopics(dynamic value) {
114+
if (value is List) {
115+
return value.map((e) => e.toString()).toList();
116+
}
117+
if (value is String && value.isNotEmpty) {
118+
try {
119+
final parsed = jsonDecode(value);
120+
if (parsed is List) {
121+
return parsed.map((e) => e.toString()).toList();
122+
}
123+
} catch (_) {}
124+
}
125+
return const [];
126+
}
127+
128+
static DateTime? _parseDate(dynamic value) {
129+
if (value == null) return null;
130+
if (value is int) {
131+
return DateTime.fromMillisecondsSinceEpoch(value);
132+
}
133+
if (value is String && value.isNotEmpty) {
134+
return DateTime.tryParse(value);
135+
}
136+
return null;
137+
}
138+
}
139+
140+
enum StoreExtensionSort {
141+
stars,
142+
installs,
143+
updated,
144+
}
145+
146+
enum StoreSortOrder {
147+
asc,
148+
desc,
149+
}

0 commit comments

Comments
 (0)