2020-04-25 13:22:19 +02:00
|
|
|
import 'dart:convert';
|
2020-04-26 17:33:59 +02:00
|
|
|
import 'dart:io';
|
2020-04-25 13:22:19 +02:00
|
|
|
|
|
|
|
|
import 'package:http/http.dart' as http;
|
2020-05-01 15:40:49 +02:00
|
|
|
import 'package:meta/meta.dart';
|
2020-06-02 16:47:46 +02:00
|
|
|
|
|
|
|
|
import 'database.dart';
|
|
|
|
|
import 'info.dart';
|
2020-04-25 13:22:19 +02:00
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
/// Credentials for a Musicus account.
|
|
|
|
|
class MusicusAccountCredentials {
|
|
|
|
|
/// The user's username.
|
|
|
|
|
final String username;
|
2020-05-08 19:02:39 +02:00
|
|
|
|
|
|
|
|
/// The user's password.
|
|
|
|
|
final String password;
|
|
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
MusicusAccountCredentials({
|
|
|
|
|
this.username,
|
2020-05-08 19:02:39 +02:00
|
|
|
this.password,
|
|
|
|
|
});
|
2020-05-13 13:43:06 +02:00
|
|
|
}
|
2020-05-08 19:02:39 +02:00
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
/// Additional information on a Musicus account.
|
|
|
|
|
class MusicusAccountDetails {
|
|
|
|
|
/// An optional email address.
|
|
|
|
|
final String email;
|
|
|
|
|
|
|
|
|
|
MusicusAccountDetails({
|
|
|
|
|
this.email,
|
|
|
|
|
});
|
2020-05-08 19:02:39 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
/// A simple http client for the Musicus server.
|
|
|
|
|
class MusicusClient {
|
2020-05-01 15:40:49 +02:00
|
|
|
/// URI scheme to use for the connection.
|
|
|
|
|
///
|
|
|
|
|
/// This will be used as the scheme parameter when creating Uri objects.
|
|
|
|
|
final String scheme;
|
|
|
|
|
|
|
|
|
|
/// The host name of the Musicus server to connect to.
|
|
|
|
|
///
|
|
|
|
|
/// This will be used as the host parameter when creating Uri objects.
|
2020-04-25 13:22:19 +02:00
|
|
|
final String host;
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// This will be used as the port parameter when creating Uri objects.
|
|
|
|
|
final int port;
|
|
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
/// Base path to the root location of the Musicus API.
|
|
|
|
|
final String basePath;
|
|
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
MusicusAccountCredentials _credentials;
|
2020-05-08 19:02:39 +02:00
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
/// Account credentials for login.
|
|
|
|
|
///
|
|
|
|
|
/// If this is null, unauthorized requests will fail.
|
|
|
|
|
MusicusAccountCredentials get credentials => _credentials;
|
|
|
|
|
set credentials(MusicusAccountCredentials credentials) {
|
|
|
|
|
_credentials = credentials;
|
2020-05-08 19:02:39 +02:00
|
|
|
_token = null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final _client = http.Client();
|
|
|
|
|
|
2020-05-08 19:02:39 +02:00
|
|
|
/// The last retrieved access token.
|
2020-05-13 13:43:06 +02:00
|
|
|
///
|
|
|
|
|
/// If this is null, a new token should be retrieved using [login] if needed.
|
2020-05-08 19:02:39 +02:00
|
|
|
String _token;
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
MusicusClient({
|
|
|
|
|
this.scheme = 'https',
|
|
|
|
|
@required this.host,
|
2020-05-01 16:30:14 +02:00
|
|
|
this.port = 443,
|
|
|
|
|
this.basePath,
|
2020-05-13 13:43:06 +02:00
|
|
|
MusicusAccountCredentials credentials,
|
2020-05-01 15:40:49 +02:00
|
|
|
}) : assert(scheme != null),
|
|
|
|
|
assert(port != null),
|
2020-05-08 19:02:39 +02:00
|
|
|
assert(host != null),
|
2020-05-13 13:43:06 +02:00
|
|
|
_credentials = credentials;
|
2020-05-01 15:40:49 +02:00
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
/// Create an URI using member variables and parameters.
|
|
|
|
|
Uri createUri({
|
|
|
|
|
@required String path,
|
|
|
|
|
Map<String, String> params,
|
|
|
|
|
}) {
|
|
|
|
|
return Uri(
|
|
|
|
|
scheme: scheme,
|
|
|
|
|
host: host,
|
|
|
|
|
port: port,
|
|
|
|
|
path: basePath != null ? basePath + path : path,
|
|
|
|
|
queryParameters: params,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
/// Create a new Musicus account.
|
|
|
|
|
///
|
|
|
|
|
/// The email address is optional. This will return true, if the action was
|
|
|
|
|
/// successful. In that case, the new credentials will automatically be
|
|
|
|
|
/// stored as under [credentials] and used for subsequent requests.
|
|
|
|
|
Future<bool> registerAccount({
|
|
|
|
|
@required String username,
|
|
|
|
|
@required String password,
|
|
|
|
|
String email,
|
|
|
|
|
}) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _client.post(
|
|
|
|
|
createUri(
|
2020-05-13 13:43:06 +02:00
|
|
|
path: '/account/register',
|
2020-05-08 19:02:39 +02:00
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
2020-05-13 13:43:06 +02:00
|
|
|
body: jsonEncode({
|
|
|
|
|
'username': username,
|
|
|
|
|
'password': password,
|
|
|
|
|
'email': email,
|
|
|
|
|
}),
|
2020-05-08 19:02:39 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.statusCode == HttpStatus.ok) {
|
2020-05-13 13:43:06 +02:00
|
|
|
_credentials = MusicusAccountCredentials(
|
|
|
|
|
username: username,
|
|
|
|
|
password: password,
|
|
|
|
|
);
|
|
|
|
|
|
2020-05-08 19:02:39 +02:00
|
|
|
_token = null;
|
2020-05-13 13:43:06 +02:00
|
|
|
|
2020-05-08 19:02:39 +02:00
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-13 13:43:06 +02:00
|
|
|
/// Get the current account details.
|
|
|
|
|
Future<MusicusAccountDetails> getAccountDetails() async {
|
|
|
|
|
assert(_credentials != null);
|
|
|
|
|
|
|
|
|
|
final response = await _authorized(
|
|
|
|
|
'GET',
|
|
|
|
|
createUri(path: '/account/details'),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
|
|
|
final json = jsonDecode(response.body);
|
|
|
|
|
|
|
|
|
|
return MusicusAccountDetails(
|
|
|
|
|
email: json['email'],
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Change the account details for the currently used user account.
|
2020-05-08 19:02:39 +02:00
|
|
|
///
|
2020-05-13 16:01:04 +02:00
|
|
|
/// If a parameter is null, it will not be changed. This will throw a
|
|
|
|
|
/// [MusicusLoginFailedException] if the account doesn't exist or the old
|
|
|
|
|
/// password was wrong.
|
|
|
|
|
Future<void> updateAccount({
|
|
|
|
|
String newEmail,
|
|
|
|
|
String newPassword,
|
|
|
|
|
}) async {
|
2020-05-13 13:43:06 +02:00
|
|
|
assert(_credentials != null);
|
|
|
|
|
|
|
|
|
|
final response = await _client.post(
|
|
|
|
|
createUri(path: '/account/details'),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode({
|
|
|
|
|
'username': _credentials.username,
|
|
|
|
|
'password': _credentials.password,
|
|
|
|
|
'newEmail': newEmail,
|
|
|
|
|
'newPassword': newPassword,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.statusCode != HttpStatus.ok) {
|
|
|
|
|
throw MusicusLoginFailedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Delete the currently used Musicus account.
|
|
|
|
|
///
|
|
|
|
|
/// This will throw a [MusicusLoginFailedException] if the user doesn't exist
|
|
|
|
|
/// or the password was wrong.
|
|
|
|
|
Future<void> deleteAccount() async {
|
|
|
|
|
assert(_credentials != null);
|
|
|
|
|
|
|
|
|
|
final response = await _client.post(
|
|
|
|
|
createUri(path: '/account/delete'),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode({
|
|
|
|
|
'username': _credentials.username,
|
|
|
|
|
'password': _credentials.password,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
|
|
|
_credentials = null;
|
|
|
|
|
_token = null;
|
|
|
|
|
} else {
|
|
|
|
|
throw MusicusLoginFailedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Retrieve an access token for the current user.
|
|
|
|
|
///
|
|
|
|
|
/// This will be called automatically, when the client calls a method that
|
|
|
|
|
/// requires it. If the login failed, a [MusicusLoginFailedException] will be
|
|
|
|
|
/// thrown.
|
|
|
|
|
Future<void> login() async {
|
|
|
|
|
assert(_credentials != null);
|
|
|
|
|
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _client.post(
|
|
|
|
|
createUri(
|
2020-05-13 13:43:06 +02:00
|
|
|
path: '/account/login',
|
2020-05-08 19:02:39 +02:00
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
2020-05-13 13:43:06 +02:00
|
|
|
body: jsonEncode({
|
|
|
|
|
'username': _credentials.username,
|
|
|
|
|
'password': _credentials.password,
|
|
|
|
|
}),
|
2020-05-08 19:02:39 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
|
|
|
_token = response.body;
|
|
|
|
|
} else {
|
|
|
|
|
throw MusicusLoginFailedException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Make a request with authorization.
|
|
|
|
|
///
|
|
|
|
|
/// This will ensure, that the request will be made with a valid
|
|
|
|
|
/// authorization header. If [user] is null, this will throw a
|
|
|
|
|
/// [MusicusNotLoggedInException]. If it is neccessary, this will login the
|
|
|
|
|
/// user and throw a [MusicusLoginFailedException] if that failed. If the
|
|
|
|
|
/// user is not authorized to perform the requested action, this will throw
|
|
|
|
|
/// a [MusicusNotAuthorizedException].
|
|
|
|
|
Future<http.Response> _authorized(String method, Uri uri,
|
|
|
|
|
{Map<String, String> headers, String body}) async {
|
2020-05-13 13:43:06 +02:00
|
|
|
if (_credentials != null) {
|
2020-05-08 19:02:39 +02:00
|
|
|
Future<http.Response> _request() async {
|
|
|
|
|
final request = http.Request(method, uri);
|
2020-05-13 13:43:06 +02:00
|
|
|
|
|
|
|
|
if (headers != null) {
|
|
|
|
|
request.headers.addAll(headers);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-08 19:02:39 +02:00
|
|
|
request.headers['Authorization'] = 'Bearer $_token';
|
2020-05-13 13:43:06 +02:00
|
|
|
|
|
|
|
|
if (body != null) {
|
|
|
|
|
request.body = body;
|
|
|
|
|
}
|
2020-05-08 19:02:39 +02:00
|
|
|
|
|
|
|
|
return await http.Response.fromStream(await _client.send(request));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
http.Response response;
|
|
|
|
|
|
|
|
|
|
if (_token != null) {
|
|
|
|
|
response = await _request();
|
|
|
|
|
if (response.statusCode == HttpStatus.unauthorized) {
|
2020-05-13 13:43:06 +02:00
|
|
|
await login();
|
2020-05-08 19:02:39 +02:00
|
|
|
response = await _request();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-05-13 13:43:06 +02:00
|
|
|
await login();
|
2020-05-08 19:02:39 +02:00
|
|
|
response = await _request();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.statusCode == HttpStatus.forbidden) {
|
|
|
|
|
throw MusicusNotAuthorizedException();
|
|
|
|
|
} else {
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw MusicusNotLoggedInException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Get a list of persons.
|
|
|
|
|
///
|
|
|
|
|
/// You can get another page using the [page] parameter. If a non empty
|
|
|
|
|
/// [search] string is provided, the persons will get filtered based on that
|
|
|
|
|
/// string.
|
|
|
|
|
Future<List<Person>> getPersons([int page, String search]) async {
|
|
|
|
|
final params = <String, String>{};
|
|
|
|
|
|
|
|
|
|
if (page != null) {
|
|
|
|
|
params['p'] = page.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (search != null) {
|
|
|
|
|
params['s'] = search;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/persons',
|
2020-05-01 16:30:14 +02:00
|
|
|
params: params,
|
2020-05-01 15:40:49 +02:00
|
|
|
));
|
2020-04-25 13:22:19 +02:00
|
|
|
|
|
|
|
|
final json = jsonDecode(response.body);
|
2020-06-03 18:37:20 +02:00
|
|
|
return json
|
|
|
|
|
.map<Person>((j) => Person.fromJson(j).copyWith(
|
|
|
|
|
sync: true,
|
|
|
|
|
synced: true,
|
|
|
|
|
))
|
|
|
|
|
.toList();
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a person by ID.
|
|
|
|
|
Future<Person> getPerson(int id) async {
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/persons/$id',
|
|
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-06-03 18:37:20 +02:00
|
|
|
return Person.fromJson(json).copyWith(
|
|
|
|
|
sync: true,
|
|
|
|
|
synced: true,
|
|
|
|
|
);
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Delete a person by ID.
|
|
|
|
|
Future<void> deletePerson(int id) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
await _authorized(
|
|
|
|
|
'DELETE',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/persons/$id',
|
|
|
|
|
),
|
|
|
|
|
);
|
2020-05-01 15:40:49 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
/// Create or update a person.
|
2020-04-26 18:31:07 +02:00
|
|
|
///
|
2020-04-26 17:33:59 +02:00
|
|
|
/// Returns true, if the operation was successful.
|
|
|
|
|
Future<bool> putPerson(Person person) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _authorized(
|
|
|
|
|
'PUT',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/persons/${person.id}',
|
|
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode(person.toJson()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response.statusCode == HttpStatus.ok;
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Get a list of instruments.
|
|
|
|
|
///
|
|
|
|
|
/// You can get another page using the [page] parameter. If a non empty
|
|
|
|
|
/// [search] string is provided, the results will get filtered based on that
|
|
|
|
|
/// string.
|
|
|
|
|
Future<List<Instrument>> getInstruments([int page, String search]) async {
|
|
|
|
|
final params = <String, String>{};
|
|
|
|
|
|
|
|
|
|
if (page != null) {
|
|
|
|
|
params['p'] = page.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (search != null) {
|
|
|
|
|
params['s'] = search;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/instruments',
|
2020-05-01 16:30:14 +02:00
|
|
|
params: params,
|
2020-05-01 15:40:49 +02:00
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-06-03 18:37:20 +02:00
|
|
|
return json
|
|
|
|
|
.map<Instrument>((j) => Instrument.fromJson(j).copyWith(
|
|
|
|
|
sync: true,
|
|
|
|
|
synced: true,
|
|
|
|
|
))
|
|
|
|
|
.toList();
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get an instrument by ID.
|
|
|
|
|
Future<Instrument> getInstrument(int id) async {
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/instruments/$id',
|
|
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-06-03 18:37:20 +02:00
|
|
|
return Instrument.fromJson(json).copyWith(
|
|
|
|
|
sync: true,
|
|
|
|
|
synced: true,
|
|
|
|
|
);
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create or update an instrument.
|
2020-04-26 18:31:07 +02:00
|
|
|
///
|
2020-04-26 17:33:59 +02:00
|
|
|
/// Returns true, if the operation was successful.
|
|
|
|
|
Future<bool> putInstrument(Instrument instrument) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _authorized(
|
|
|
|
|
'PUT',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/instruments/${instrument.id}',
|
|
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode(instrument.toJson()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response.statusCode == HttpStatus.ok;
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Delete an instrument by ID.
|
|
|
|
|
Future<void> deleteInstrument(int id) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
await _authorized(
|
|
|
|
|
'DELETE',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/instruments/$id',
|
|
|
|
|
),
|
|
|
|
|
);
|
2020-05-01 15:40:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a list of works written by the person with the ID [personId].
|
|
|
|
|
///
|
|
|
|
|
/// You can get another page using the [page] parameter. If a non empty
|
|
|
|
|
/// [search] string is provided, the results will get filtered based on that
|
|
|
|
|
/// string.
|
|
|
|
|
Future<List<WorkInfo>> getWorks(int personId,
|
|
|
|
|
[int page, String search]) async {
|
|
|
|
|
final params = <String, String>{};
|
|
|
|
|
|
|
|
|
|
if (page != null) {
|
|
|
|
|
params['p'] = page.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (search != null) {
|
|
|
|
|
params['s'] = search;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/persons/$personId/works',
|
2020-05-01 16:30:14 +02:00
|
|
|
params: params,
|
2020-05-01 15:40:49 +02:00
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-07-17 20:12:50 +02:00
|
|
|
return json.map<WorkInfo>((j) => WorkInfo.fromJson(j, sync: true)).toList();
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a work by ID.
|
2020-04-25 17:30:37 +02:00
|
|
|
Future<WorkInfo> getWork(int id) async {
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/works/$id',
|
|
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-07-17 20:12:50 +02:00
|
|
|
return WorkInfo.fromJson(json, sync: true);
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Delete a work by ID.
|
|
|
|
|
Future<void> deleteWork(int id) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
await _authorized(
|
|
|
|
|
'DELETE',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/works/$id',
|
|
|
|
|
),
|
|
|
|
|
);
|
2020-05-01 15:40:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a list of recordings of the work with the ID [workId].
|
|
|
|
|
///
|
|
|
|
|
/// You can get another page using the [page] parameter.
|
|
|
|
|
Future<List<RecordingInfo>> getRecordings(int workId, [int page]) async {
|
|
|
|
|
final params = <String, String>{};
|
|
|
|
|
|
|
|
|
|
if (page != null) {
|
|
|
|
|
params['p'] = page.toString();
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/works/$workId/recordings',
|
2020-05-01 16:30:14 +02:00
|
|
|
params: params,
|
2020-05-01 15:40:49 +02:00
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-07-17 20:12:50 +02:00
|
|
|
return json
|
|
|
|
|
.map<RecordingInfo>((j) => RecordingInfo.fromJson(j, sync: true))
|
|
|
|
|
.toList();
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create or update a work.
|
2020-04-26 18:31:07 +02:00
|
|
|
///
|
2020-04-26 17:33:59 +02:00
|
|
|
/// Returns true, if the operation was successful.
|
|
|
|
|
Future<bool> putWork(WorkInfo workInfo) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _authorized(
|
|
|
|
|
'PUT',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/works/${workInfo.work.id}',
|
|
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode(workInfo.toJson()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response.statusCode == HttpStatus.ok;
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Get a list of ensembles.
|
|
|
|
|
///
|
|
|
|
|
/// You can get another page using the [page] parameter. If a non empty
|
|
|
|
|
/// [search] string is provided, the results will get filtered based on that
|
|
|
|
|
/// string.
|
|
|
|
|
Future<List<Ensemble>> getEnsembles([int page, String search]) async {
|
|
|
|
|
final params = <String, String>{};
|
|
|
|
|
|
|
|
|
|
if (page != null) {
|
|
|
|
|
params['p'] = page.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (search != null) {
|
|
|
|
|
params['s'] = search;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/ensembles',
|
2020-05-01 16:30:14 +02:00
|
|
|
params: params,
|
2020-05-01 15:40:49 +02:00
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-06-03 18:37:20 +02:00
|
|
|
return json
|
|
|
|
|
.map<Ensemble>((j) => Ensemble.fromJson(j).copyWith(
|
|
|
|
|
sync: true,
|
|
|
|
|
synced: true,
|
|
|
|
|
))
|
|
|
|
|
.toList();
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get an ensemble by ID.
|
|
|
|
|
Future<Ensemble> getEnsemble(int id) async {
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/ensembles/$id',
|
|
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-06-03 18:37:20 +02:00
|
|
|
return Ensemble.fromJson(json).copyWith(
|
|
|
|
|
sync: true,
|
|
|
|
|
synced: true,
|
|
|
|
|
);
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create or update an ensemble.
|
2020-04-26 18:31:07 +02:00
|
|
|
///
|
2020-04-26 17:33:59 +02:00
|
|
|
/// Returns true, if the operation was successful.
|
|
|
|
|
Future<bool> putEnsemble(Ensemble ensemble) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _authorized(
|
|
|
|
|
'PUT',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/ensembles/${ensemble.id}',
|
|
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode(ensemble.toJson()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response.statusCode == HttpStatus.ok;
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Delete an ensemble by ID.
|
|
|
|
|
Future<void> deleteEnsemble(int id) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
await _authorized(
|
|
|
|
|
'DELETE',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/ensembles/$id',
|
|
|
|
|
),
|
|
|
|
|
);
|
2020-05-01 15:40:49 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
/// Get a recording by ID.
|
2020-04-25 17:30:37 +02:00
|
|
|
Future<RecordingInfo> getRecording(int id) async {
|
2020-05-01 16:30:14 +02:00
|
|
|
final response = await _client.get(createUri(
|
2020-05-01 15:40:49 +02:00
|
|
|
path: '/recordings/$id',
|
|
|
|
|
));
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
final json = jsonDecode(response.body);
|
2020-07-17 20:12:50 +02:00
|
|
|
return RecordingInfo.fromJson(json, sync: true);
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create or update a recording.
|
2020-04-26 18:31:07 +02:00
|
|
|
///
|
2020-04-26 17:33:59 +02:00
|
|
|
/// Returns true, if the operation was successful.
|
|
|
|
|
Future<bool> putRecording(RecordingInfo recordingInfo) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
final response = await _authorized(
|
|
|
|
|
'PUT',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/recordings/${recordingInfo.recording.id}',
|
|
|
|
|
),
|
|
|
|
|
headers: {'Content-Type': 'application/json'},
|
|
|
|
|
body: jsonEncode(recordingInfo.toJson()),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response.statusCode == HttpStatus.ok;
|
2020-04-25 13:22:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:40:49 +02:00
|
|
|
/// Delete a recording by ID.
|
|
|
|
|
Future<void> deleteRecording(int id) async {
|
2020-05-08 19:02:39 +02:00
|
|
|
await _authorized(
|
|
|
|
|
'DELETE',
|
|
|
|
|
createUri(
|
|
|
|
|
path: '/recordings/$id',
|
|
|
|
|
),
|
|
|
|
|
);
|
2020-05-01 15:40:49 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-25 13:22:19 +02:00
|
|
|
/// Close the internal http client.
|
|
|
|
|
void dispose() {
|
|
|
|
|
_client.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-08 19:02:39 +02:00
|
|
|
|
|
|
|
|
class MusicusLoginFailedException implements Exception {
|
|
|
|
|
MusicusLoginFailedException();
|
|
|
|
|
|
|
|
|
|
String toString() => 'MusicusLoginFailedException: The username or password '
|
|
|
|
|
'was wrong.';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MusicusNotLoggedInException implements Exception {
|
|
|
|
|
MusicusNotLoggedInException();
|
|
|
|
|
|
|
|
|
|
String toString() =>
|
|
|
|
|
'MusicusNotLoggedInException: The user must be logged in to perform '
|
|
|
|
|
'this action.';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MusicusNotAuthorizedException implements Exception {
|
|
|
|
|
MusicusNotAuthorizedException();
|
|
|
|
|
|
|
|
|
|
String toString() =>
|
|
|
|
|
'MusicusNotAuthorizedException: The logged in user is not allowed to '
|
|
|
|
|
'perform this action.';
|
|
|
|
|
}
|