diff --git a/SUBSONIC.md b/SUBSONIC.md
index b2319566..34a8053a 100644
--- a/SUBSONIC.md
+++ b/SUBSONIC.md
@@ -25,6 +25,7 @@ The following extra fields are implemented:
* `musicBrainzId`
* `originalReleaseDate`
* `releaseTypes`
+ * `userRating`
* `Child` response:
* `albumArtists`
* `artists`
diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt
index 2d3c2a16..6e8abadf 100644
--- a/src/libs/database/CMakeLists.txt
+++ b/src/libs/database/CMakeLists.txt
@@ -11,6 +11,9 @@ add_library(lmsdatabase SHARED
impl/TrackArtistLink.cpp
impl/TrackFeatures.cpp
impl/TrackList.cpp
+ impl/RatedArtist.cpp
+ impl/RatedRelease.cpp
+ impl/RatedTrack.cpp
impl/Release.cpp
impl/ScanSettings.cpp
impl/Session.cpp
diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp
index 6f11ba0e..f9d8e431 100644
--- a/src/libs/database/impl/Migration.cpp
+++ b/src/libs/database/impl/Migration.cpp
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
- static constexpr Version LMS_DATABASE_VERSION{ 63 };
+ static constexpr Version LMS_DATABASE_VERSION{ 64 };
}
VersionInfo::VersionInfo()
@@ -673,6 +673,47 @@ SELECT
session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1");
}
+ void migrateFromV63(Session& session)
+ {
+ // Add a rated entities
+
+ session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "rated_artist" (
+ "id" integer primary key autoincrement,
+ "version" integer not null,
+ "rating" integer not null,
+ "last_updated" text,
+ "artist_id" bigint,
+ "user_id" bigint,
+ constraint "fk_rated_artist_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred,
+ constraint "fk_rated_artist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
+))");
+
+ session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "rated_release" (
+ "id" integer primary key autoincrement,
+ "version" integer not null,
+ "rating" integer not null,
+ "last_updated" text,
+ "release_id" bigint,
+ "user_id" bigint,
+ constraint "fk_rated_release_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
+ constraint "fk_rated_release_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
+))");
+
+ session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "rated_track" (
+ "id" integer primary key autoincrement,
+ "version" integer not null,
+ "rating" bigint not null,
+ "last_updated" text,
+ "track_id" bigint,
+ "user_id" bigint,
+ constraint "fk_rated_track_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
+ constraint "fk_rated_track_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred
+))");
+
+ // Drop badly named index, will be recreated
+ session.getDboSession()->execute("DROP INDEX IF EXISTS listen_user_backend_date_time");
+ }
+
bool doDbMigration(Session& session)
{
static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -712,6 +753,7 @@ SELECT
{ 60, migrateFromV60 },
{ 61, migrateFromV61 },
{ 62, migrateFromV62 },
+ { 63, migrateFromV63 },
};
bool migrationPerformed{};
diff --git a/src/libs/database/impl/RatedArtist.cpp b/src/libs/database/impl/RatedArtist.cpp
new file mode 100644
index 00000000..f953c27a
--- /dev/null
+++ b/src/libs/database/impl/RatedArtist.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "database/RatedArtist.hpp"
+
+#include
+
+#include "database/Artist.hpp"
+#include "database/Session.hpp"
+#include "database/User.hpp"
+
+#include "IdTypeTraits.hpp"
+#include "Utils.hpp"
+
+namespace lms::db
+{
+ RatedArtist::RatedArtist(ObjectPtr artist, ObjectPtr user)
+ : _artist{ getDboPtr(artist) }
+ , _user{ getDboPtr(user) }
+ {
+ }
+
+ RatedArtist::pointer RatedArtist::create(Session& session, ObjectPtr artist, ObjectPtr user)
+ {
+ return session.getDboSession()->add(std::unique_ptr{ new RatedArtist{ artist, user } });
+ }
+
+ std::size_t RatedArtist::getCount(Session& session)
+ {
+ session.checkReadTransaction();
+ return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM rated_artist"));
+ }
+
+ RatedArtist::pointer RatedArtist::find(Session& session, RatedArtistId id)
+ {
+ session.checkReadTransaction();
+ return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id));
+ }
+
+ RatedArtist::pointer RatedArtist::find(Session& session, ArtistId artistId, UserId userId)
+ {
+ session.checkReadTransaction();
+ return utils::fetchQuerySingleResult(session.getDboSession()->find().where("artist_id = ?").bind(artistId).where("user_id = ?").bind(userId));
+ }
+
+ void RatedArtist::find(Session& session, const FindParameters& params, std::function func)
+ {
+ session.checkReadTransaction();
+
+ auto query{ session.getDboSession()->query>("SELECT r_a FROM rated_artist r_a") };
+
+ if (params.user.isValid())
+ query.where("r_a.user_id = ?").bind(params.user);
+
+ utils::forEachQueryRangeResult(query, params.range, func);
+ }
+
+ void RatedArtist::setLastUpdated(const Wt::WDateTime& lastUpdated)
+ {
+ _lastUpdated = utils::normalizeDateTime(lastUpdated);
+ }
+} // namespace lms::db
diff --git a/src/libs/database/impl/RatedRelease.cpp b/src/libs/database/impl/RatedRelease.cpp
new file mode 100644
index 00000000..69cc51be
--- /dev/null
+++ b/src/libs/database/impl/RatedRelease.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "database/RatedRelease.hpp"
+
+#include
+
+#include "database/Release.hpp"
+#include "database/Session.hpp"
+#include "database/User.hpp"
+
+#include "IdTypeTraits.hpp"
+#include "Utils.hpp"
+
+namespace lms::db
+{
+ RatedRelease::RatedRelease(ObjectPtr release, ObjectPtr user)
+ : _release{ getDboPtr(release) }
+ , _user{ getDboPtr(user) }
+ {
+ }
+
+ RatedRelease::pointer RatedRelease::create(Session& session, ObjectPtr release, ObjectPtr user)
+ {
+ return session.getDboSession()->add(std::unique_ptr{ new RatedRelease{ release, user } });
+ }
+
+ std::size_t RatedRelease::getCount(Session& session)
+ {
+ session.checkReadTransaction();
+ return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM rated_release"));
+ }
+
+ RatedRelease::pointer RatedRelease::find(Session& session, RatedReleaseId id)
+ {
+ session.checkReadTransaction();
+ return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id));
+ }
+
+ RatedRelease::pointer RatedRelease::find(Session& session, ReleaseId releaseId, UserId userId)
+ {
+ session.checkReadTransaction();
+ return utils::fetchQuerySingleResult(session.getDboSession()->find().where("release_id = ?").bind(releaseId).where("user_id = ?").bind(userId));
+ }
+
+ void RatedRelease::find(Session& session, const FindParameters& params, std::function func)
+ {
+ session.checkReadTransaction();
+
+ auto query{ session.getDboSession()->query>("SELECT r_r FROM rated_release r_r") };
+
+ if (params.user.isValid())
+ query.where("r_r.user_id = ?").bind(params.user);
+
+ utils::forEachQueryRangeResult(query, params.range, func);
+ }
+
+ void RatedRelease::setLastUpdated(const Wt::WDateTime& lastUpdated)
+ {
+ _lastUpdated = utils::normalizeDateTime(lastUpdated);
+ }
+} // namespace lms::db
diff --git a/src/libs/database/impl/RatedTrack.cpp b/src/libs/database/impl/RatedTrack.cpp
new file mode 100644
index 00000000..5cf3bda5
--- /dev/null
+++ b/src/libs/database/impl/RatedTrack.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include "database/RatedTrack.hpp"
+
+#include
+
+#include "database/Session.hpp"
+#include "database/Track.hpp"
+#include "database/User.hpp"
+
+#include "IdTypeTraits.hpp"
+#include "Utils.hpp"
+
+namespace lms::db
+{
+ RatedTrack::RatedTrack(ObjectPtr