From ad022a664097130e8104d634c862653c38d51e6c Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sat, 25 Apr 2020 17:30:37 +0200 Subject: [PATCH] database, server: Simpler and more efficient API --- client/lib/src/client.dart | 49 ++++----- database/lib/musicus_database.dart | 3 +- database/lib/src/database.dart | 89 +++++++++++++++++ database/lib/src/info.dart | 149 ++++++++++++++++++++++++++++ server/lib/src/compositions.dart | 2 +- server/lib/src/performances.dart | 14 --- server/lib/src/recordings.dart | 5 +- server/lib/src/server.dart | 7 +- server/lib/src/work_parts.dart | 14 --- server/lib/src/work_recordings.dart | 2 +- server/lib/src/works.dart | 5 +- 11 files changed, 271 insertions(+), 68 deletions(-) create mode 100644 database/lib/src/info.dart delete mode 100644 server/lib/src/performances.dart delete mode 100644 server/lib/src/work_parts.dart diff --git a/client/lib/src/client.dart b/client/lib/src/client.dart index dccc760..4404c2d 100644 --- a/client/lib/src/client.dart +++ b/client/lib/src/client.dart @@ -59,40 +59,38 @@ class MusicusClient { } /// Get all works composed by the person with the ID [personId]. - Future> getWorks(int personId) async { + Future> getWorks(int personId) async { final response = await _client.get('$host/persons/$personId/works'); final json = jsonDecode(response.body); - return json.map((j) => Work.fromJson(j)).toList(); + return json.map((j) => WorkInfo.fromJson(j)).toList(); } /// Get a work by ID. - Future getWork(int id) async { + Future getWork(int id) async { final response = await _client.get('$host/works/$id'); final json = jsonDecode(response.body); - return Work.fromJson(json); - } - - /// Get all work parts of the work with the ID [workId]. - Future> getParts(int workId) async { - final response = await _client.get('$host/works/$workId/parts'); - final json = jsonDecode(response.body); - return json.map((j) => Work.fromJson(j)).toList(); + return WorkInfo.fromJson(json); } /// Get all recordings of the work with the ID [workId]. - Future> getRecordings(int workId) async { + Future> getRecordings(int workId) async { final response = await _client.get('$host/works/$workId/recordings'); final json = jsonDecode(response.body); - return json.map((j) => Recording.fromJson(j)).toList(); + return json.map((j) => RecordingInfo.fromJson(j)).toList(); } /// Create or update a work. - Future putWork(WorkData data) async { - await _client.put( + /// + /// The new or updated work is returned. + Future putWork(WorkData data) async { + final response = await _client.put( '$host/works/${data.data.work.id}', headers: {'Content-Type': 'application/json'}, body: jsonEncode(data.toJson()), ); + + final json = jsonDecode(response.body); + return WorkInfo.fromJson(json); } /// Get a list of all ensembles. @@ -119,27 +117,24 @@ class MusicusClient { } /// Get a recording by ID. - Future getRecording(int id) async { + Future getRecording(int id) async { final response = await _client.get('$host/recordings/$id'); final json = jsonDecode(response.body); - return Recording.fromJson(json); - } - - /// Get all performances within the recording with the ID [recordingId]. - Future> getPerformances(int recordingId) async { - final response = - await _client.get('$host/recordings/$recordingId/performances'); - final json = jsonDecode(response.body); - return json.map((j) => Performance.fromJson(j)).toList(); + return RecordingInfo.fromJson(json); } /// Create or update a recording. - Future putRecording(RecordingData data) async { - await _client.put( + /// + /// The new or updated recording is returned. + Future putRecording(RecordingData data) async { + final response = await _client.put( '$host/recordings/${data.recording.id}', headers: {'Content-Type': 'application/json'}, body: jsonEncode(data.toJson()), ); + + final json = jsonDecode(response.body); + return RecordingInfo.fromJson(json); } /// Close the internal http client. diff --git a/database/lib/musicus_database.dart b/database/lib/musicus_database.dart index 63a040b..de7af64 100644 --- a/database/lib/musicus_database.dart +++ b/database/lib/musicus_database.dart @@ -1 +1,2 @@ -export 'src/database.dart'; \ No newline at end of file +export 'src/database.dart'; +export 'src/info.dart'; \ No newline at end of file diff --git a/database/lib/src/database.dart b/database/lib/src/database.dart index 0a912bd..e91f6c1 100644 --- a/database/lib/src/database.dart +++ b/database/lib/src/database.dart @@ -2,6 +2,8 @@ import 'dart:math'; import 'package:moor/moor.dart'; +import 'info.dart'; + part 'database.g.dart'; final _random = Random(DateTime.now().millisecondsSinceEpoch); @@ -99,6 +101,50 @@ class Database extends _$Database { await into(instruments).insert(instrument, orReplace: true); } + /// Retrieve more information on an already queried work. + Future getWorkInfo(Work work) async { + final id = work.id; + + final composers = await composersByWork(id).get(); + final instruments = await instrumentsByWork(id).get(); + + final List parts = []; + for (final part in await workParts(id).get()) { + parts.add(PartInfo( + work: part, + composer: part.composer != null + ? await personById(part.composer).getSingle() + : null, + instruments: await instrumentsByWork(id).get(), + )); + } + + return WorkInfo( + work: work, + instruments: instruments, + composers: composers, + parts: parts, + ); + } + + /// Get all available information on a work. + Future getWork(int id) async { + final work = await workById(id).getSingle(); + return await getWorkInfo(work); + } + + /// Get information on all works written by the person with ID [personId]. + Future> getWorks(int personId) async { + final works = await worksByComposer(personId).get(); + + final List result = []; + for (final work in works) { + result.add(await getWorkInfo(work)); + } + + return result; + } + Future updateWork(WorkData data) async { await transaction(() async { final workId = data.data.work.id; @@ -137,4 +183,47 @@ class Database extends _$Database { } }); } + + /// Retreive more information on an already queried recording. + Future getRecordingInfo(Recording recording) async { + final id = recording.id; + + final List performances = []; + for (final performance in await performancesByRecording(id).get()) { + performances.add(PerformanceInfo( + person: performance.person != null + ? await personById(performance.person).getSingle() + : null, + ensemble: performance.ensemble != null + ? await ensembleById(performance.ensemble).getSingle() + : null, + role: performance.role != null + ? await instrumentById(performance.role).getSingle() + : null, + )); + } + + return RecordingInfo( + recording: recording, + performances: performances, + ); + } + + /// Get all available information on a recording. + Future getRecording(int id) async { + final recording = await recordingById(id).getSingle(); + return await getRecordingInfo(recording); + } + + /// Get information on all recordings of the work with ID [workId]. + Future> getRecordings(int workId) async { + final recordings = await recordingsByWork(workId).get(); + + final List result = []; + for (final recording in recordings) { + result.add(await getRecordingInfo(recording)); + } + + return result; + } } diff --git a/database/lib/src/info.dart b/database/lib/src/info.dart new file mode 100644 index 0000000..48aca2c --- /dev/null +++ b/database/lib/src/info.dart @@ -0,0 +1,149 @@ +import 'database.dart'; + +/// A bundle of all available information on a work part. +class PartInfo { + /// The work part itself. + final Work work; + + /// A list of instruments. + /// + /// This will include the instruments, that are specific to this part. + final List instruments; + + /// The composer of this part. + /// + /// This is null, if this part doesn't have a specific composer. + final Person composer; + + PartInfo({ + this.work, + this.instruments, + this.composer, + }); + + factory PartInfo.fromJson(Map json) => PartInfo( + work: Work.fromJson(json['work']), + instruments: json['instruments'] + .map((j) => Instrument.fromJson(j)) + .toList(), + composer: + json['composer'] != null ? Person.fromJson(json['composer']) : null, + ); + + Map toJson() => { + 'work': work.toJson(), + 'instruments': instruments.map((i) => i.toJson()).toList(), + 'composers': composer?.toJson(), + }; +} + +/// A bundle information on a work. +/// +/// This includes all available information except for recordings of this work. +class WorkInfo { + /// The work itself. + final Work work; + + /// A list of instruments. + /// + /// This will not the include the instruments, that are specific to the work + /// parts. + final List instruments; + + /// A list of persons, which will include all part composers. + final List composers; + + /// All available information on the work parts. + final List parts; + + WorkInfo({ + this.work, + this.instruments, + this.composers, + this.parts, + }); + + factory WorkInfo.fromJson(Map json) => WorkInfo( + work: Work.fromJson(json['work']), + instruments: json['instruments'] + .map((j) => Instrument.fromJson(j)) + .toList(), + composers: + json['composers'].map((j) => Person.fromJson(j)).toList(), + parts: + json['parts'].map((j) => WorkInfo.fromJson(j)).toList(), + ); + + Map toJson() => { + 'work': work.toJson(), + 'instruments': instruments.map((i) => i.toJson()).toList(), + 'composers': composers.map((c) => c.toJson()).toList(), + 'parts': parts.map((c) => c.toJson()).toList(), + }; +} + +/// All available information on a performance within a recording. +class PerformanceInfo { + /// The performing person. + /// + /// This will be null, if this is an ensemble. + final Person person; + + /// The performing ensemble. + /// + /// This will be null, if this is a person. + final Ensemble ensemble; + + /// The instrument/role or null. + final Instrument role; + + PerformanceInfo({ + this.person, + this.ensemble, + this.role, + }); + + factory PerformanceInfo.fromJson(Map json) => + PerformanceInfo( + person: json['person'] != null ? Person.fromJson(json['person']) : null, + ensemble: json['ensemble'] != null + ? Ensemble.fromJson(json['ensemble']) + : null, + role: json['role'] != null ? Instrument.fromJson(json['role']) : null, + ); + + Map toJson() => { + 'person': person?.toJson(), + 'ensemble': ensemble?.toJson(), + 'role': role?.toJson(), + }; +} + +/// All available information on a recording. +/// +/// This doesn't include the recorded work, because probably it's already +/// available. +class RecordingInfo { + /// The recording itself. + final Recording recording; + + /// Information on the performances within this recording. + final List performances; + + RecordingInfo({ + this.recording, + this.performances, + }); + + factory RecordingInfo.fromJson(Map json) => RecordingInfo( + recording: Recording.fromJson(json['recording']), + performances: json['performances'] + .map((j) => PerformanceInfo.fromJson(j)) + .toList(), + ); + + Map toJson() => { + 'recording': recording.toJson(), + 'performances': performances.map((p) => p.toJson()).toList(), + }; +} diff --git a/server/lib/src/compositions.dart b/server/lib/src/compositions.dart index 92c3ab8..80f4d94 100644 --- a/server/lib/src/compositions.dart +++ b/server/lib/src/compositions.dart @@ -8,7 +8,7 @@ class CompositionsController extends ResourceController { @Operation.get('id') Future getWorks(@Bind.path('id') int id) async { - final works = db.worksByComposer(id).get(); + final works = await db.getWorks(id); return Response.ok(works); } } diff --git a/server/lib/src/performances.dart b/server/lib/src/performances.dart deleted file mode 100644 index 79382a0..0000000 --- a/server/lib/src/performances.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:aqueduct/aqueduct.dart'; -import 'package:musicus_database/musicus_database.dart'; - -class PerformancesController extends ResourceController { - final Database db; - - PerformancesController(this.db); - - @Operation.get('id') - Future getPerformances(@Bind.path('id') int id) async { - final performances = await db.performancesByRecording(id).get(); - return Response.ok(performances); - } -} diff --git a/server/lib/src/recordings.dart b/server/lib/src/recordings.dart index 5fe35ea..9115faa 100644 --- a/server/lib/src/recordings.dart +++ b/server/lib/src/recordings.dart @@ -8,7 +8,7 @@ class RecordingsController extends ResourceController { @Operation.get('id') Future getRecording(@Bind.path('id') int id) async { - final recording = await db.recordingById(id).getSingle(); + final recording = await db.getRecording(id); if (recording != null) { return Response.ok(recording); } else { @@ -21,6 +21,7 @@ class RecordingsController extends ResourceController { @Bind.path('id') int id, @Bind.body() Map json) async { final data = RecordingData.fromJson(json); await db.updateRecording(data); - return Response.ok(null); + + return Response.ok(await db.getRecording(id)); } } diff --git a/server/lib/src/server.dart b/server/lib/src/server.dart index a31821c..c032bcb 100644 --- a/server/lib/src/server.dart +++ b/server/lib/src/server.dart @@ -9,10 +9,8 @@ import 'compositions.dart'; import 'configuration.dart'; import 'ensembles.dart'; import 'instruments.dart'; -import 'performances.dart'; import 'persons.dart'; import 'recordings.dart'; -import 'work_parts.dart'; import 'works.dart'; class MusicusServer extends ApplicationChannel { @@ -35,10 +33,7 @@ class MusicusServer extends ApplicationChannel { ..route('/persons/:id/works').link(() => CompositionsController(db)) ..route('/instruments/[:id]').link(() => InstrumentsController(db)) ..route('/works/:id').link(() => WorksController(db)) - ..route('/works/:id/parts').link(() => WorkPartsController(db)) ..route('/works/:id/recordings').link(() => WorkRecordingsController(db)) ..route('/ensembles/[:id]').link(() => EnsemblesController(db)) - ..route('/recordings/:id').link(() => RecordingsController(db)) - ..route('/recordings/:id/performances') - .link(() => PerformancesController(db)); + ..route('/recordings/:id').link(() => RecordingsController(db)); } diff --git a/server/lib/src/work_parts.dart b/server/lib/src/work_parts.dart deleted file mode 100644 index 92a7d1d..0000000 --- a/server/lib/src/work_parts.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:aqueduct/aqueduct.dart'; -import 'package:musicus_database/musicus_database.dart'; - -class WorkPartsController extends ResourceController { - final Database db; - - WorkPartsController(this.db); - - @Operation.get('id') - Future getParts(@Bind.path('id') int id) async { - final parts = await db.workParts(id).get(); - return Response.ok(parts); - } -} diff --git a/server/lib/src/work_recordings.dart b/server/lib/src/work_recordings.dart index 0eb2e1f..7ed7682 100644 --- a/server/lib/src/work_recordings.dart +++ b/server/lib/src/work_recordings.dart @@ -8,7 +8,7 @@ class WorkRecordingsController extends ResourceController { @Operation.get('id') Future getRecordings(@Bind.path('id') int id) async { - final recordings = await db.recordingsByWork(id).get(); + final recordings = await db.getRecordings(id); return Response.ok(recordings); } } diff --git a/server/lib/src/works.dart b/server/lib/src/works.dart index eca1a0e..fbdbc1e 100644 --- a/server/lib/src/works.dart +++ b/server/lib/src/works.dart @@ -8,7 +8,7 @@ class WorksController extends ResourceController { @Operation.get('id') Future getWork(@Bind.path('id') int id) async { - final work = await db.workById(id).getSingle(); + final work = await db.getWork(id); if (work != null) { return Response.ok(work); } else { @@ -21,6 +21,7 @@ class WorksController extends ResourceController { @Bind.path('id') int id, @Bind.body() Map json) async { final data = WorkData.fromJson(json); await db.updateWork(data); - return Response.ok(null); + + return Response.ok(await db.getWork(id)); } }