-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathcelest_db_studio.dart
More file actions
130 lines (115 loc) · 4.12 KB
/
celest_db_studio.dart
File metadata and controls
130 lines (115 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import 'dart:convert';
import 'package:celest_core/_internal.dart';
import 'package:celest_db_studio/src/driver.dart';
import 'package:celest_db_studio/src/template.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
export 'package:celest_db_studio/src/driver.dart';
/// {@template celest_db_studio.celest_db_studio}
/// A simple server which serves an instance of Outerbase Studio as an embedded
/// iframe and responds to query requests from the iframe.
///
/// The server connects to a database using the provided [databaseUri] and
/// proxies requests from the iframe to the database.
/// {@endtemplate}
final class CelestDbStudio {
/// {@macro celest_db_studio.celest_db_studio}
static Future<CelestDbStudio> create({
String pageTitle = defaultTitle,
required Uri databaseUri,
String? authToken,
}) async {
final Driver driver;
switch (databaseUri) {
case Uri(scheme: 'libsql' || 'https' || 'http'):
driver = await HranaDriver.connect(databaseUri, jwtToken: authToken);
case Uri(scheme: 'file', path: '/:memory:'):
driver = NativeDriver.memory();
case Uri(scheme: 'file'):
driver = NativeDriver.file(databaseUri.toFilePath());
default:
throw ArgumentError.value(
databaseUri.toString(),
'databaseUri',
'Unsupported database URI scheme: ${databaseUri.scheme}. '
'Supported schemes are: libsql, https, http, file',
);
}
return CelestDbStudio.from(pageTitle: pageTitle, driver: driver);
}
CelestDbStudio.from({this.pageTitle = defaultTitle, required Driver driver})
: _driver = driver;
/// The default title of the page.
static const String defaultTitle = 'DB Studio';
/// The title of the page.
///
/// If not provided, the [defaultTitle] will be used.
final String pageTitle;
final Driver _driver;
/// The rendered HTML for the index page.
late final String _indexHtml = indexHtml
.replaceAll('{{ title }}', pageTitle)
.replaceAll('{{ script }}', indexJs);
late final Router _router =
Router()
..get('/', _index)
..get('/index.html', _index)
..post('/query', _query);
late final Handler _handler = (const Pipeline()
..addMiddleware(_corsMiddleware))
.addHandler(_router.call);
static Handler _corsMiddleware(Handler innerHandler) {
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Credentials': 'true',
};
return (Request request) async {
if (request.method == 'OPTIONS') {
return Response.ok(null, headers: corsHeaders);
}
final response = await innerHandler(request);
return response.change(headers: corsHeaders);
};
}
/// Handles the given [request] if possible.
Future<Response> call(Request request) async {
return _handler(request);
}
/// Serves the studio HTML.
Future<Response> _index(Request request) async {
return Response.ok(_indexHtml, headers: {'Content-Type': 'text/html'});
}
/// Responds to query requests from the Outerbase Studio iframe.
Future<Response> _query(Request request) async {
final json = await JsonUtf8.decodeStream(request.read());
if (json
case {
'id': final int id,
'type': final String type,
'statements': [final String statement],
} ||
{
'id': final int id,
'type': final String type,
'statement': final String statement,
}) {
try {
final result = await _driver.execute(statement);
return Response.ok(
jsonEncode({
'type': type,
'id': id,
'data': type == 'transaction' ? [result.toJson()] : result.toJson(),
}),
headers: {'Content-Type': 'application/json'},
);
} on Object catch (e) {
return Response.internalServerError(body: e.toString());
}
} else {
return Response.badRequest(body: 'Invalid request format');
}
}
}