Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions migrations/20251114014715_record_dates.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
ALTER TABLE record_modifications DROP COLUMN date;

CREATE OR REPLACE FUNCTION audit_record_modification() RETURNS trigger AS $record_modification_trigger$
DECLARE
progress_change SMALLINT;
video_change VARCHAR(200);
status_change RECORD_STATUS;
player_change INT;
demon_change INTEGER;
BEGIN
if (OLD.progress <> NEW.progress) THEN
progress_change = OLD.progress;
END IF;

IF (OLD.video <> NEW.video) THEN
video_change = OLD.video;
END IF;

IF (OLD.status_ <> NEW.status_) THEN
status_change = OLD.status_;
END IF;

IF (OLD.player <> NEW.player) THEN
player_change = OLD.player;
END IF;

IF (OLD.demon <> NEW.demon) THEN
demon_change = OLD.demon;
END IF;

INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon)
(SELECT id, NEW.id, progress_change, video_change, status_change, player_change, demon_change
FROM active_user LIMIT 1);

RETURN NEW;
END;
$record_modification_trigger$ LANGUAGE plpgsql;

ALTER TABLE records DROP COLUMN date;
50 changes: 50 additions & 0 deletions migrations/20251114014715_record_dates.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
ALTER TABLE records ADD COLUMN date TIMESTAMP WITHOUT TIME ZONE DEFAULT (NOW() AT TIME ZONE 'utc') NOT NULL;

UPDATE records
SET date = record_additions.time
FROM record_additions
WHERE records.id = record_additions.id;

-- audit logs
ALTER TABLE record_modifications ADD COLUMN date TIMESTAMP WITHOUT TIME ZONE;

CREATE OR REPLACE FUNCTION audit_record_modification() RETURNS trigger AS $record_modification_trigger$
DECLARE
progress_change SMALLINT;
video_change VARCHAR(200);
status_change RECORD_STATUS;
player_change INT;
demon_change INTEGER;
date_change TIMESTAMP WITHOUT TIME ZONE;
BEGIN
if (OLD.progress <> NEW.progress) THEN
progress_change = OLD.progress;
END IF;

IF (OLD.video <> NEW.video) THEN
video_change = OLD.video;
END IF;

IF (OLD.status_ <> NEW.status_) THEN
status_change = OLD.status_;
END IF;

IF (OLD.player <> NEW.player) THEN
player_change = OLD.player;
END IF;

IF (OLD.demon <> NEW.demon) THEN
demon_change = OLD.demon;
END IF;

IF (OLD.date <> NEW.date) THEN
date_change = OLD.date;
END IF;

INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon, date)
(SELECT id, NEW.id, progress_change, video_change, status_change, player_change, demon_change, date_change
FROM active_user LIMIT 1);

RETURN NEW;
END;
$record_modification_trigger$ LANGUAGE plpgsql;
36 changes: 36 additions & 0 deletions pointercrate-demonlist-pages/src/account/records.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl AccountPageTab for RecordsPage {
(change_video_dialog())
(change_holder_dialog())
(change_demon_dialog(&demons[..]))
(change_date_dialog())
}
}
}
Expand Down Expand Up @@ -172,6 +173,15 @@ fn record_manager(demons: &[Demon]) -> Markup {
span #record-submitter {}
}
}
div.stats-container.flex.space {
span {
b {
i.fa.fa-pencil-alt.clickable #record-date-pen aria-hidden = "true" {} " " (tr("record-date"))
}
br;
span #record-date {}
}
}
span.button.red.hover #record-delete style = "margin: 15px auto 0px" {(tr("record-viewer.delete"))};
}
}
Expand Down Expand Up @@ -407,3 +417,29 @@ fn change_demon_dialog(demons: &[Demon]) -> Markup {
}
}
}

fn change_date_dialog() -> Markup {
html! {
div.overlay.closable {
div.dialog #record-date-dialog {
span.plus.cross.hover {}
h2.underlined.pad {
(tr("record-date-dialog"))
}
p style = "max-width: 400px"{
(tr("record-date-dialog.info"))
}
form.flex.col novalidate = "" {
p.info-red.output {}
p.info-green.output {}
span.form-input #record-date-edit {
label for = "date" {(tr("record-date-dialog.date-field")) }
input name = "date" type = "datetime-local";
p.error {}
}
input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = (tr("record-date-dialog.submit"));
}
}
}
}
}
7 changes: 7 additions & 0 deletions pointercrate-demonlist-pages/static/ftl/en-us/record.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ record-demon = Demon
record-holder = Record Holder
record-progress = Progress
record-submitter = Submitter ID
record-date = Submission Date

## Records tab (user area)
records = Records
Expand Down Expand Up @@ -94,6 +95,12 @@ record-progress-dialog = Change record progress
.progress-validator-stepmismatch = Record progress mustn't be a decimal
.progress-validator-valuemissing = Please enter a progress value

record-date-dialog = Change record submission date
.info = Change the submission date of this record. This can be modified to reorder records in demon pages.
.date-field = Date:

.submit = Edit

# The giant information box below the record manager, split
# into different sections here
#
Expand Down
15 changes: 15 additions & 0 deletions pointercrate-demonlist-pages/static/js/account/records.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class RecordManager extends Paginator {
this._progress = document.getElementById("record-progress");
this._submitter = document.getElementById("record-submitter");
this._notes = document.getElementById("record-notes");
this._date = document.getElementById("record-date");

this.dropdown = new Dropdown(
document
Expand Down Expand Up @@ -84,6 +85,7 @@ class RecordManager extends Paginator {
this.output
);
this.initDemonDialog();
this.initDateDialog();

document
.getElementById("record-copy-info")
Expand Down Expand Up @@ -183,6 +185,16 @@ class RecordManager extends Paginator {
);
}

initDateDialog() {
setupEditorDialog(
new FormDialog("record-date-dialog"),
"record-date-pen",
new PaginatorEditorBackend(this, true),
this.output,
(date) => ({ date: new Date(date.date).toISOString() })
);
}

onReceive(response) {
super.onReceive(response);

Expand Down Expand Up @@ -227,6 +239,9 @@ class RecordManager extends Paginator {
this._progress.innerText = this.currentObject.progress + "%";
this._submitter.innerText = this.currentObject.submitter.id;

let date = new Date(this.currentObject.date);
this._date.innerText = date.toLocaleString();

// this is introducing race conditions. Oh well.
return get("/api/v1/records/" + this.currentObject.id + "/notes/").then(
(response) => {
Expand Down
1 change: 1 addition & 0 deletions pointercrate-demonlist/sql/record_by_id.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ SELECT progress,
CASE WHEN players.link_banned THEN NULL ELSE records.video::text END,
CASE WHEN players.link_banned THEN NULL ELSE records.raw_footage::text END,
status_::text AS "status!: String" ,
date AS "date!",
players.id AS player_id, players.name AS "player_name: String", players.banned AS player_banned,
demons.id AS demon_id, demons.name AS "demon_name: String", demons.position,
submitters.submitter_id AS submitter_id, submitters.banned AS submitter_banned
Expand Down
5 changes: 4 additions & 1 deletion pointercrate-demonlist/src/record/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
record::{FullRecord, MinimalRecordD, MinimalRecordP, RecordStatus},
submitter::Submitter,
};
use chrono::{NaiveDateTime, TimeZone, Utc};
use futures::stream::StreamExt;
use sqlx::{Error, PgConnection};

Expand All @@ -15,6 +16,7 @@ struct FetchedRecord {
video: Option<String>,
raw_footage: Option<String>,
status: String,
date: NaiveDateTime,
player_id: i32,
player_name: String,
player_banned: bool,
Expand Down Expand Up @@ -52,6 +54,7 @@ impl FullRecord {
id: row.submitter_id,
banned: row.submitter_banned,
}),
date: Utc.from_utc_datetime(&row.date),
}),

Err(Error::RowNotFound) => Err(DemonlistError::RecordNotFound { record_id: id }),
Expand Down Expand Up @@ -106,7 +109,7 @@ pub async fn approved_records_on(demon: &MinimalDemon, connection: &mut PgConnec
Fetched,
r#"SELECT records.id, progress, CASE WHEN players.link_banned THEN NULL ELSE video::text END, players.id AS player_id,
players.name, players.banned, nation::TEXT, iso_country_code::TEXT FROM records INNER JOIN players ON records.player = players.id LEFT OUTER JOIN nationalities ON nationality = iso_country_code WHERE status_ = 'APPROVED' AND
records.demon = $1 ORDER BY progress DESC, id ASC"#,
records.demon = $1 ORDER BY progress DESC, date ASC"#,
demon.id
)
.fetch(connection);
Expand Down
2 changes: 2 additions & 0 deletions pointercrate-demonlist/src/record/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub use self::{
post::Submission,
};
use crate::{demon::MinimalDemon, error::Result, nationality::Nationality, player::DatabasePlayer, submitter::Submitter};
use chrono::{DateTime, Utc};
use derive_more::Display;
use pointercrate_core::etag::Taggable;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -128,6 +129,7 @@ pub struct FullRecord {
pub demon: MinimalDemon,
pub submitter: Option<Submitter>,
pub raw_footage: Option<String>,
pub date: DateTime<Utc>,
}

impl Taggable for FullRecord {
Expand Down
18 changes: 18 additions & 0 deletions pointercrate-demonlist/src/record/patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
player::DatabasePlayer,
record::{FullRecord, RecordStatus},
};
use chrono::{DateTime, Utc};
use log::{info, warn};
use pointercrate_core::{
error::CoreError,
Expand Down Expand Up @@ -31,6 +32,9 @@ pub struct PatchRecord {

#[serde(default, deserialize_with = "non_nullable")]
demon_id: Option<i32>,

#[serde(default, deserialize_with = "non_nullable")]
date: Option<DateTime<Utc>>,
}

impl FullRecord {
Expand Down Expand Up @@ -69,6 +73,10 @@ impl FullRecord {
_ => (),
}

if let Some(date) = data.date {
self.set_date(date, connection).await?;
}

// Not all record update require recomputing scores (for example, changing status from "submitted" to "under consideration")
// but the logic for correctly determining this is hard, and updating scores of individual players cheap, so we do not bother.
self.player.update_score(connection).await?;
Expand Down Expand Up @@ -395,4 +403,14 @@ impl FullRecord {

Ok(())
}

pub async fn set_date(&mut self, date: DateTime<Utc>, connection: &mut PgConnection) -> Result<()> {
sqlx::query!("UPDATE records SET date = $1 WHERE id = $2", date.naive_utc(), self.id)
.execute(&mut *connection)
.await?;

self.date = date;

Ok(())
}
}
11 changes: 6 additions & 5 deletions pointercrate-demonlist/src/record/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
record::{FullRecord, RecordStatus},
submitter::Submitter,
};
use chrono::{TimeZone, Utc};
use derive_more::Display;
use log::debug;
use serde::Deserialize;
Expand Down Expand Up @@ -172,8 +173,8 @@ impl NormalizedSubmission {

impl ValidatedSubmission {
pub async fn create(self, submitter: Submitter, connection: &mut PgConnection) -> Result<FullRecord> {
let id = sqlx::query!(
"INSERT INTO records (progress, video, status_, player, submitter, demon, raw_footage) VALUES ($1, $2::TEXT, 'SUBMITTED', $3, $4, $5, $6) RETURNING id",
let row = sqlx::query!(
"INSERT INTO records (progress, video, status_, player, submitter, demon, raw_footage) VALUES ($1, $2::TEXT, 'SUBMITTED', $3, $4, $5, $6) RETURNING id, date",
self.progress,
self.video,
self.player.id,
Expand All @@ -182,18 +183,18 @@ impl ValidatedSubmission {
self.raw_footage
)
.fetch_one(&mut *connection)
.await?
.id;
.await?;

let mut record = FullRecord {
id,
id: row.id,
progress: self.progress,
video: self.video,
raw_footage: self.raw_footage,
status: RecordStatus::Submitted,
player: self.player,
demon: self.demon,
submitter: Some(submitter),
date: Utc.from_utc_datetime(&row.date),
};

// Dealing with different status and upholding their invariant is complicated, we should not
Expand Down
Loading