diff --git a/client/Message.cs b/client/Message.cs index b1a7a7b..2b1e951 100644 --- a/client/Message.cs +++ b/client/Message.cs @@ -17,6 +17,7 @@ internal class Message { public int PositiveVotes { get; init; } public int NegativeVotes { get; init; } + public int UserVote { get; set; } internal Vector3 Position => new(this.X, this.Y, this.Z); } @@ -35,6 +36,7 @@ internal class MessageWithTerritory { public int PositiveVotes { get; init; } public int NegativeVotes { get; init; } + public int UserVote { get; set; } internal Vector3 Position => new(this.X, this.Y, this.Z); @@ -48,6 +50,7 @@ internal class MessageWithTerritory { Text = message.Text, PositiveVotes = message.PositiveVotes, NegativeVotes = message.NegativeVotes, + UserVote = message.UserVote, }; } } diff --git a/client/OrangeGuidanceTomestone.yaml b/client/OrangeGuidanceTomestone.yaml index f488041..4d2673f 100755 --- a/client/OrangeGuidanceTomestone.yaml +++ b/client/OrangeGuidanceTomestone.yaml @@ -1,6 +1,6 @@ name: Orange Guidance Tomestone author: ascclemes -punchline: Try finger, but hole +punchline: Leave messages like in Souls games. Great chest ahead. description: |- Dark Souls-like messaging system. repo_url: https://git.anna.lgbt/ascclemens/OrangeGuidanceTomestone diff --git a/client/Plugin.cs b/client/Plugin.cs index e3ae3d8..8060f8d 100644 --- a/client/Plugin.cs +++ b/client/Plugin.cs @@ -1,4 +1,5 @@ -using Dalamud.Game; +using Dalamud.Data; +using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.Command; using Dalamud.IoC; @@ -18,6 +19,9 @@ public class Plugin : IDalamudPlugin { [PluginService] internal CommandManager CommandManager { get; init; } + [PluginService] + internal DataManager DataManager { get; init; } + [PluginService] internal Framework Framework { get; init; } diff --git a/client/Ui/MainWindowTabs/MessageList.cs b/client/Ui/MainWindowTabs/MessageList.cs index 6a47bd5..f200580 100644 --- a/client/Ui/MainWindowTabs/MessageList.cs +++ b/client/Ui/MainWindowTabs/MessageList.cs @@ -1,4 +1,6 @@ +using Dalamud.Utility; using ImGuiNET; +using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using OrangeGuidanceTomestone.Helpers; @@ -23,10 +25,14 @@ internal class MessageList : ITab { this.MessagesMutex.Wait(); foreach (var message in this.Messages) { + var territory = this.Plugin.DataManager.GetExcelSheet()?.GetRow(message.Territory); + var territoryName = territory?.PlaceName.Value?.Name?.ToDalamudString().TextValue ?? "???"; + ImGui.TextUnformatted(message.Text); ImGui.TreePush(); - ImGui.TextUnformatted($"Likes: {message.PositiveVotes}"); - ImGui.TextUnformatted($"Dislikes: {message.NegativeVotes}"); + ImGui.TextUnformatted($"Location: {territoryName}"); + var appraisals = Math.Max(0, message.PositiveVotes - message.NegativeVotes); + ImGui.TextUnformatted($"Appraisals: {appraisals} ({message.PositiveVotes} - {message.NegativeVotes}"); if (ImGui.Button($"Delete##{message.Id}")) { this.Delete(message.Id); } diff --git a/client/Ui/Viewer.cs b/client/Ui/Viewer.cs index 33f9389..9015586 100644 --- a/client/Ui/Viewer.cs +++ b/client/Ui/Viewer.cs @@ -77,32 +77,50 @@ internal class Viewer { ImGui.TextUnformatted(message.Text); ImGui.PopTextWrapPos(); - ImGui.TextUnformatted($"Appraisals: {message.PositiveVotes - message.NegativeVotes}"); + var appraisals = Math.Max(0, message.PositiveVotes - message.NegativeVotes); + ImGui.TextUnformatted($"Appraisals: {appraisals:N0}"); - if (ImGui.Button("Like")) { + void Vote(int way) { Task.Run(async () => { - await ServerHelper.SendRequest( + var resp = await ServerHelper.SendRequest( this.Plugin.Config.ApiKey, HttpMethod.Patch, $"/messages/{message.Id}/votes", "application/json", - new StringContent("1") + new StringContent(way.ToString()) ); + + if (resp.IsSuccessStatusCode) { + message.UserVote = way; + } }); } + var vote = message.UserVote; + if (vote == 1) { + ImGui.BeginDisabled(); + } + + if (ImGui.Button("Like")) { + Vote(1); + } + + if (vote == 1) { + ImGui.EndDisabled(); + } + ImGui.SameLine(); + if (vote == -1) { + ImGui.BeginDisabled(); + } + if (ImGui.Button("Dislike")) { - Task.Run(async () => { - await ServerHelper.SendRequest( - this.Plugin.Config.ApiKey, - HttpMethod.Patch, - $"/messages/{message.Id}/votes", - "application/json", - new StringContent("-1") - ); - }); + Vote(-1); + } + + if (vote == -1) { + ImGui.EndDisabled(); } } diff --git a/server/src/message.rs b/server/src/message.rs index 0020b66..c376ac3 100644 --- a/server/src/message.rs +++ b/server/src/message.rs @@ -18,17 +18,6 @@ pub struct Message { pub word_2_word: Option, } -#[derive(Debug, Deserialize)] -pub struct VoteMessage { - pub post_id: Uuid, - pub vote: u8, -} - -#[derive(Debug, Deserialize)] -pub struct DeleteMessage { - pub post_id: Uuid, -} - #[derive(Debug, Serialize)] pub struct RetrievedMessage { pub id: String, @@ -38,6 +27,7 @@ pub struct RetrievedMessage { pub message: String, pub positive_votes: i32, pub negative_votes: i32, + pub user_vote: i64, } #[derive(Debug, Serialize)] @@ -50,4 +40,5 @@ pub struct RetrievedMessageTerritory { pub message: String, pub positive_votes: i32, pub negative_votes: i32, + pub user_vote: i64, } diff --git a/server/src/web.rs b/server/src/web.rs index da4108b..9ac1156 100644 --- a/server/src/web.rs +++ b/server/src/web.rs @@ -15,6 +15,7 @@ mod erase; mod get_location; mod vote; mod get_mine; +mod get_message; pub fn routes(state: Arc) -> BoxedFilter<(impl Reply, )> { register::register(Arc::clone(&state)) @@ -22,6 +23,7 @@ pub fn routes(state: Arc) -> BoxedFilter<(impl Reply, )> { .or(write::write(Arc::clone(&state))) .or(erase::erase(Arc::clone(&state))) .or(vote::vote(Arc::clone(&state))) + .or(get_message::get_message(Arc::clone(&state))) .or(get_location::get_location(Arc::clone(&state))) .or(get_mine::get_mine(Arc::clone(&state))) .recover(handle_rejection) @@ -59,6 +61,7 @@ pub enum WebError { InvalidPackId, InvalidIndex, TooManyMessages, + NoSuchMessage, } impl Reject for WebError {} @@ -78,6 +81,7 @@ async fn handle_rejection(err: Rejection) -> Result { WebError::InvalidPackId => StatusCode::NOT_FOUND, WebError::InvalidIndex => StatusCode::NOT_FOUND, WebError::TooManyMessages => StatusCode::BAD_REQUEST, + WebError::NoSuchMessage => StatusCode::NOT_FOUND, } } else { eprintln!("{:#?}", err); diff --git a/server/src/web/get_location.rs b/server/src/web/get_location.rs index efb9ebf..566f2cb 100644 --- a/server/src/web/get_location.rs +++ b/server/src/web/get_location.rs @@ -20,7 +20,7 @@ pub fn get_location(state: Arc) -> BoxedFilter<(impl Reply, )> { async fn logic(state: Arc, id: i64, location: u32) -> Result { // TODO: when we're not just returning all results, make sure own messages are always present - let id = location as i64; + let location = location as i64; let messages = sqlx::query_as!( RetrievedMessage, // language=sqlite @@ -31,12 +31,15 @@ async fn logic(state: Arc, id: i64, location: u32) -> Result) -> BoxedFilter<(impl Reply, )> { + warp::get() + .and(warp::path("messages")) + .and(warp::path::param()) + .and(warp::path::end()) + .and(super::get_id(Arc::clone(&state))) + .and_then(move |message_id: Uuid, id: i64| logic(Arc::clone(&state), id, message_id)) + .boxed() +} + +async fn logic(state: Arc, id: i64, message_id: Uuid) -> Result { + let message_id = message_id.simple().to_string(); + let message = sqlx::query_as!( + RetrievedMessageTerritory, + // language=sqlite + r#" + select m.id, + m.territory, + m.x, + m.y, + m.z, + m.message, + coalesce(sum(v.vote between 0 and 1), 0) as positive_votes, + coalesce(sum(v.vote between -1 and 0), 0) as negative_votes, + v2.vote as user_vote + from messages m + left join votes v on m.id = v.message + left join votes v2 on m.id = v2.message and v2.user = ? + where m.id = ? + group by m.id"#, + id, + message_id, + ) + .fetch_optional(&state.db) + .await + .context("could not get message from database") + .map_err(AnyhowRejection) + .map_err(warp::reject::custom)?; + + let message = message.ok_or_else(|| warp::reject::custom(WebError::NoSuchMessage))?; + Ok(warp::reply::json(&message)) +} diff --git a/server/src/web/get_mine.rs b/server/src/web/get_mine.rs index 46514c0..9869c9b 100644 --- a/server/src/web/get_mine.rs +++ b/server/src/web/get_mine.rs @@ -29,12 +29,15 @@ async fn logic(state: Arc, id: i64) -> Result { m.z, m.message, coalesce(sum(v.vote between 0 and 1), 0) as positive_votes, - coalesce(sum(v.vote between -1 and 0), 0) as negative_votes + coalesce(sum(v.vote between -1 and 0), 0) as negative_votes, + v2.vote as user_vote from messages m left join votes v on m.id = v.message + left join votes v2 on m.id = v2.message and v2.user = ? where m.user = ? group by m.id"#, id, + id, ) .fetch_all(&state.db) .await