mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
client: Implement authorization
This commit is contained in:
parent
9e0c6fa00a
commit
fa2e9ebacd
1 changed files with 222 additions and 76 deletions
|
|
@ -5,6 +5,30 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:musicus_database/musicus_database.dart';
|
import 'package:musicus_database/musicus_database.dart';
|
||||||
|
|
||||||
|
/// A user of the Musicus API.
|
||||||
|
class User {
|
||||||
|
/// Username.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// An optional email address.
|
||||||
|
final String email;
|
||||||
|
|
||||||
|
/// The user's password.
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
User({
|
||||||
|
this.name,
|
||||||
|
this.email,
|
||||||
|
this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'name': name,
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// A simple http client for the Musicus server.
|
/// A simple http client for the Musicus server.
|
||||||
class MusicusClient {
|
class MusicusClient {
|
||||||
/// URI scheme to use for the connection.
|
/// URI scheme to use for the connection.
|
||||||
|
|
@ -23,16 +47,30 @@ class MusicusClient {
|
||||||
/// Base path to the root location of the Musicus API.
|
/// Base path to the root location of the Musicus API.
|
||||||
final String basePath;
|
final String basePath;
|
||||||
|
|
||||||
|
User _user;
|
||||||
|
|
||||||
|
/// The user to login.
|
||||||
|
User get user => _user;
|
||||||
|
set user(User user) {
|
||||||
|
_user = user;
|
||||||
|
_token = null;
|
||||||
|
}
|
||||||
|
|
||||||
final _client = http.Client();
|
final _client = http.Client();
|
||||||
|
|
||||||
|
/// The last retrieved access token.
|
||||||
|
String _token;
|
||||||
|
|
||||||
MusicusClient({
|
MusicusClient({
|
||||||
this.scheme = 'https',
|
this.scheme = 'https',
|
||||||
@required this.host,
|
@required this.host,
|
||||||
this.port = 443,
|
this.port = 443,
|
||||||
this.basePath,
|
this.basePath,
|
||||||
|
User user,
|
||||||
}) : assert(scheme != null),
|
}) : assert(scheme != null),
|
||||||
assert(port != null),
|
assert(port != null),
|
||||||
assert(host != null);
|
assert(host != null),
|
||||||
|
_user = user;
|
||||||
|
|
||||||
/// Create an URI using member variables and parameters.
|
/// Create an URI using member variables and parameters.
|
||||||
Uri createUri({
|
Uri createUri({
|
||||||
|
|
@ -48,6 +86,91 @@ class MusicusClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new user.
|
||||||
|
///
|
||||||
|
/// This will return true, if the action was successful. Subsequent requests
|
||||||
|
/// will automatically be made as the new user.
|
||||||
|
Future<bool> register(User newUser) async {
|
||||||
|
final response = await _client.post(
|
||||||
|
createUri(
|
||||||
|
path: '/register',
|
||||||
|
),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode(newUser.toJson()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
_user = newUser;
|
||||||
|
_token = null;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve an access token for [user].
|
||||||
|
///
|
||||||
|
/// The token will land in [_token]. If the login failed, a
|
||||||
|
/// [MusicusLoginFailedException] will be thrown.
|
||||||
|
Future<void> _login() async {
|
||||||
|
final response = await _client.post(
|
||||||
|
createUri(
|
||||||
|
path: '/login',
|
||||||
|
),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode(user.toJson()),
|
||||||
|
);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if (_user != null) {
|
||||||
|
Future<http.Response> _request() async {
|
||||||
|
final request = http.Request(method, uri);
|
||||||
|
request.headers.addAll(headers);
|
||||||
|
request.headers['Authorization'] = 'Bearer $_token';
|
||||||
|
request.body = body;
|
||||||
|
|
||||||
|
return await http.Response.fromStream(await _client.send(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Response response;
|
||||||
|
|
||||||
|
if (_token != null) {
|
||||||
|
response = await _request();
|
||||||
|
if (response.statusCode == HttpStatus.unauthorized) {
|
||||||
|
await _login();
|
||||||
|
response = await _request();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await _login();
|
||||||
|
response = await _request();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.forbidden) {
|
||||||
|
throw MusicusNotAuthorizedException();
|
||||||
|
} else {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw MusicusNotLoggedInException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a list of persons.
|
/// Get a list of persons.
|
||||||
///
|
///
|
||||||
/// You can get another page using the [page] parameter. If a non empty
|
/// You can get another page using the [page] parameter. If a non empty
|
||||||
|
|
@ -85,28 +208,28 @@ class MusicusClient {
|
||||||
|
|
||||||
/// Delete a person by ID.
|
/// Delete a person by ID.
|
||||||
Future<void> deletePerson(int id) async {
|
Future<void> deletePerson(int id) async {
|
||||||
await _client.delete(createUri(
|
await _authorized(
|
||||||
path: '/persons/$id',
|
'DELETE',
|
||||||
));
|
createUri(
|
||||||
|
path: '/persons/$id',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create or update a person.
|
/// Create or update a person.
|
||||||
///
|
///
|
||||||
/// Returns true, if the operation was successful.
|
/// Returns true, if the operation was successful.
|
||||||
Future<bool> putPerson(Person person) async {
|
Future<bool> putPerson(Person person) async {
|
||||||
try {
|
final response = await _authorized(
|
||||||
final response = await _client.put(
|
'PUT',
|
||||||
createUri(
|
createUri(
|
||||||
path: '/persons/${person.id}',
|
path: '/persons/${person.id}',
|
||||||
),
|
),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(person.toJson()),
|
body: jsonEncode(person.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
} on Exception {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of instruments.
|
/// Get a list of instruments.
|
||||||
|
|
@ -148,26 +271,26 @@ class MusicusClient {
|
||||||
///
|
///
|
||||||
/// Returns true, if the operation was successful.
|
/// Returns true, if the operation was successful.
|
||||||
Future<bool> putInstrument(Instrument instrument) async {
|
Future<bool> putInstrument(Instrument instrument) async {
|
||||||
try {
|
final response = await _authorized(
|
||||||
final response = await _client.put(
|
'PUT',
|
||||||
createUri(
|
createUri(
|
||||||
path: '/instruments/${instrument.id}',
|
path: '/instruments/${instrument.id}',
|
||||||
),
|
),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(instrument.toJson()),
|
body: jsonEncode(instrument.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
} on Exception {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete an instrument by ID.
|
/// Delete an instrument by ID.
|
||||||
Future<void> deleteInstrument(int id) async {
|
Future<void> deleteInstrument(int id) async {
|
||||||
await _client.delete(createUri(
|
await _authorized(
|
||||||
path: '/instruments/$id',
|
'DELETE',
|
||||||
));
|
createUri(
|
||||||
|
path: '/instruments/$id',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of works written by the person with the ID [personId].
|
/// Get a list of works written by the person with the ID [personId].
|
||||||
|
|
@ -208,9 +331,12 @@ class MusicusClient {
|
||||||
|
|
||||||
/// Delete a work by ID.
|
/// Delete a work by ID.
|
||||||
Future<void> deleteWork(int id) async {
|
Future<void> deleteWork(int id) async {
|
||||||
await _client.delete(createUri(
|
await _authorized(
|
||||||
path: '/works/$id',
|
'DELETE',
|
||||||
));
|
createUri(
|
||||||
|
path: '/works/$id',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of recordings of the work with the ID [workId].
|
/// Get a list of recordings of the work with the ID [workId].
|
||||||
|
|
@ -236,19 +362,16 @@ class MusicusClient {
|
||||||
///
|
///
|
||||||
/// Returns true, if the operation was successful.
|
/// Returns true, if the operation was successful.
|
||||||
Future<bool> putWork(WorkInfo workInfo) async {
|
Future<bool> putWork(WorkInfo workInfo) async {
|
||||||
try {
|
final response = await _authorized(
|
||||||
final response = await _client.put(
|
'PUT',
|
||||||
createUri(
|
createUri(
|
||||||
path: '/works/${workInfo.work.id}',
|
path: '/works/${workInfo.work.id}',
|
||||||
),
|
),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(workInfo.toJson()),
|
body: jsonEncode(workInfo.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
} on Exception {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a list of ensembles.
|
/// Get a list of ensembles.
|
||||||
|
|
@ -290,26 +413,26 @@ class MusicusClient {
|
||||||
///
|
///
|
||||||
/// Returns true, if the operation was successful.
|
/// Returns true, if the operation was successful.
|
||||||
Future<bool> putEnsemble(Ensemble ensemble) async {
|
Future<bool> putEnsemble(Ensemble ensemble) async {
|
||||||
try {
|
final response = await _authorized(
|
||||||
final response = await _client.put(
|
'PUT',
|
||||||
createUri(
|
createUri(
|
||||||
path: '/ensembles/${ensemble.id}',
|
path: '/ensembles/${ensemble.id}',
|
||||||
),
|
),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(ensemble.toJson()),
|
body: jsonEncode(ensemble.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
} on Exception {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete an ensemble by ID.
|
/// Delete an ensemble by ID.
|
||||||
Future<void> deleteEnsemble(int id) async {
|
Future<void> deleteEnsemble(int id) async {
|
||||||
await _client.delete(createUri(
|
await _authorized(
|
||||||
path: '/ensembles/$id',
|
'DELETE',
|
||||||
));
|
createUri(
|
||||||
|
path: '/ensembles/$id',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a recording by ID.
|
/// Get a recording by ID.
|
||||||
|
|
@ -326,26 +449,26 @@ class MusicusClient {
|
||||||
///
|
///
|
||||||
/// Returns true, if the operation was successful.
|
/// Returns true, if the operation was successful.
|
||||||
Future<bool> putRecording(RecordingInfo recordingInfo) async {
|
Future<bool> putRecording(RecordingInfo recordingInfo) async {
|
||||||
try {
|
final response = await _authorized(
|
||||||
final response = await _client.put(
|
'PUT',
|
||||||
createUri(
|
createUri(
|
||||||
path: '/recordings/${recordingInfo.recording.id}',
|
path: '/recordings/${recordingInfo.recording.id}',
|
||||||
),
|
),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(recordingInfo.toJson()),
|
body: jsonEncode(recordingInfo.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.statusCode == HttpStatus.ok;
|
return response.statusCode == HttpStatus.ok;
|
||||||
} on Exception {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a recording by ID.
|
/// Delete a recording by ID.
|
||||||
Future<void> deleteRecording(int id) async {
|
Future<void> deleteRecording(int id) async {
|
||||||
await _client.delete(createUri(
|
await _authorized(
|
||||||
path: '/recordings/$id',
|
'DELETE',
|
||||||
));
|
createUri(
|
||||||
|
path: '/recordings/$id',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the internal http client.
|
/// Close the internal http client.
|
||||||
|
|
@ -353,3 +476,26 @@ class MusicusClient {
|
||||||
_client.close();
|
_client.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.';
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue