Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement audit log #1108

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
21 changes: 21 additions & 0 deletions schema/patches/16.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
BEGIN;

CREATE TABLE myradio.audit_log (
log_entry_id BIGSERIAL PRIMARY KEY,
entry_type TEXT,
target_class TEXT,
target_id BIGINT,
actor_id INTEGER REFERENCES public.member (memberid),
payload JSONB,
entry_time TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_audit_types ON myradio.audit_log (entry_type);
CREATE INDEX idx_audit_class_id ON myradio.audit_log (target_class, target_id);
CREATE INDEX idx_audit_actor ON myradio.audit_log (actor_id);

UPDATE myradio.schema
SET value = 16
WHERE attr='version';

COMMIT;
17 changes: 17 additions & 0 deletions src/Classes/MyRadio/AuditLogTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace MyRadio\MyRadio;

class AuditLogTypes
{
public const Created = 'created';
public const Edited = 'edited';

public const MetaUpdated = 'meta_updated';

public const Scheduled = 'scheduled';
public const Rejected = 'rejected';
public const Cancelled = 'cancelled';
public const SeasonCancelled = 'season_cancelled';
public const Moved = 'moved';
}
144 changes: 144 additions & 0 deletions src/Classes/ServiceAPI/MyRadio_AuditLog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace MyRadio\ServiceAPI;

use MyRadio\MyRadio\CoreUtils;
use MyRadio\ServiceAPI\MyRadio_User;
use MyRadio\MyRadioException;

class MyRadio_AuditLog extends ServiceAPI
{
private const BASE_SQL = "SELECT log_entry_id, entry_type, target_class, target_id, actor_id, payload, entry_time FROM myradio.audit_log";

private int $entry_id;

private string $entry_type;

/** @var string|null */
private $target_class;

private int $target_id;

private int $actor_id;

private array $payload;

private int $entry_time;

protected function __construct(array $data)
{
$this->entry_id = (int) $data['log_entry_id'];
$this->entry_type = $data['entry_type'];
$this->target_class = $data['target_class'];
$this->target_id = (int) $data['target_id'];
$this->actor_id = (int) $data['actor_id'];
$this->payload = json_decode($data['payload'], true);
$this->entry_time = strtotime($data['entry_time']);
}

public function getID()
{
return $this->entry_id;
}

public function getEventType()
{
return $this->entry_type;
}

public function getTargetClass()
{
return $this->target_class;
}

public function getTargetID()
{
return $this->target_id;
}

/**
* @return MyRadio\ServiceAPI\MyRadio_User
*/
public function getActor()
{
return MyRadio_User::getInstance($this->actor_id);
}

public function getPayload()
{
return $this->payload;
}

public function getEntryTime()
{
return $this->entry_time;
}

/**
* Logs an event to the audit log, attributing it to the currently signed in user.
*
* If no $target_class is given, uses the calling class name.
*/
public static function log(string $type, int $target_id, array $payload, string $target_class='')
{
if ($target_class === '')
{
$caller = debug_backtrace(!DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS,2)[1];
$target_class = $caller['class'];
}
$actor_id = MyRadio_User::getCurrentOrSystemUser()->getID();
$sql = "INSERT INTO myradio.audit_log (entry_type, target_class, target_id, actor_id, payload, entry_time) VALUES ($1, $2, $3, $4, $5::JSONB, NOW())";
self::$db->query(
$sql,
[$type, $target_class, $target_id, $actor_id, json_encode($payload)]
);
}

/**
* @return self[]
*/
public static function getEvents(int $since, int $until, array $query = [])
{
$sql = self::BASE_SQL . ' WHERE entry_time >= $1 AND entry_time <= $2';
$paramId = 0;
$params = [CoreUtils::getTimestamp($since), CoreUtils::getTimestamp($until)];
if (in_array('event_type', $query))
{
$sql .= " AND event_type = $$paramId";
$params[] = $query['event_type'];
$paramId++;
}
if (in_array('target_type', $query))
{
$sql .= " AND target_type = $$paramId";
$params[] = $query['target_type'];
$paramId++;
}
if (in_array('actor_id', $query))
{
$sql .= " AND actor_id = $$paramId";
$params[] = $query['actor_id'];
$paramId++;
}

$rows = self::$db->fetchAll($sql, $params);
$result = [];
foreach ($rows as $row)
{
$result[] = new self($row);
}
return $result;
}

protected static function factory($itemid)
{
$sql = self::BASE_SQL . " WHERE eventid = $1";

$result = self::$db->fetchOne($sql, [$itemid]);
if (empty($result)) {
throw new MyRadioException("Event $itemid does not exist", 404);
}

return new self($result);
}
}
2 changes: 1 addition & 1 deletion src/Classes/ServiceAPI/MyRadio_Creditable.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ trait MyRadio_Creditable
* @param MyRadio_Metadata_Common $parent Used when there is inheritance enabled
* for this object. In this case credits are merged.
*
* @return type
* @return array
*/
public function getCredits(\MyRadio\ServiceAPI\MyRadio_Metadata_Common $parent = null)
{
Expand Down
54 changes: 54 additions & 0 deletions src/Classes/ServiceAPI/MyRadio_Season.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

use MyRadio\Config;
use MyRadio\MyRadioException;
use MyRadio\MyRadio\AuditLogTypes;
use MyRadio\MyRadio\CoreUtils;
use MyRadio\MyRadio\URLUtils;
use MyRadio\MyRadio\MyRadioForm;
use MyRadio\MyRadio\MyRadioFormField;
use MyRadio\MyRadioEmail;
use MyRadio\ServiceAPI\MyRadio_AuditLog;

/**
* The Season class is used to create, view and manipulate Seasons within the new MyRadio Scheduler Format.
Expand Down Expand Up @@ -314,6 +316,12 @@ public static function create($params = [])
);
}

MyRadio_AuditLog::log(
AuditLogTypes::Created,
$newSeason->season_id,
['show_id' => $newSeason->show_id, 'term_id' => $newSeason->term_id, 'requested_weeks' => $newSeason->requested_weeks, 'requested_times' => $newSeason->requested_times],
);

return $newSeason;
}

Expand Down Expand Up @@ -618,6 +626,12 @@ public function setCredits($users, $credittypes, $table = null, $pkey = null)
$r = parent::setCredits($users, $credittypes, 'schedule.show_credit', 'show_id');
$this->updateCacheObject();

MyRadio_AuditLog::log(
AuditLogTypes::Edited,
$this->season_id,
['credits' => ['users' => $users, 'credit_types' => $credittypes]]
);

return $r;
}

Expand Down Expand Up @@ -703,6 +717,12 @@ public function reject($reason, $notify_user = true)
);
}

MyRadio_AuditLog::log(
AuditLogTypes::Rejected,
$this->season_id,
['reason' => $reason]
);

self::$db->query('COMMIT');
}

Expand Down Expand Up @@ -755,6 +775,12 @@ public function setMeta($string_key, $value, $effective_from = null, $effective_
$this->metadata[$string_key] = $value;
$this->updateCacheObject();

MyRadio_AuditLog::log(
AuditLogTypes::MetaUpdated,
$this->season_id,
['key' => $string_key, 'value' => $value]
);

return $r;
}

Expand Down Expand Up @@ -809,6 +835,11 @@ public function setSubtype($subtypeId)
self::$db->query('UPDATE schedule.show_season_subtype SET show_subtype_id = $1 WHERE season_id = $1', [
$subtypeId, $this->season_id
]);
MyRadio_AuditLog::log(
AuditLogTypes::Edited,
$this->season_id,
['subtype' => $this->getSubtype()->toDataSource()]
);
}

/**
Expand All @@ -824,6 +855,11 @@ public function setSubtypeByName($subtypeName)
WHERE season_id = $1',
[$this->season_id, $subtypeName]
);
MyRadio_AuditLog::log(
AuditLogTypes::Edited,
$this->season_id,
['subtype' => $this->getSubtype()->toDataSource()]
);
}

/**
Expand All @@ -832,6 +868,11 @@ public function setSubtypeByName($subtypeName)
public function clearSubtype()
{
self::$db->query('DELETE FROM schedule.show_season_subtype WHERE season_id = $1', [$this->season_id]);
MyRadio_AuditLog::log(
AuditLogTypes::Edited,
$this->season_id,
['subtype' => null]
);
}

public function getRequestedTimes()
Expand Down Expand Up @@ -1107,6 +1148,13 @@ public function schedule($params)
self::$cache->delete('MyRadioScheduleFor'.$weekAndYear[0].'W'.$weekAndYear[1]);
}
}

MyRadio_AuditLog::log(
AuditLogTypes::Scheduled,
$this->season_id,
['times' => explode("\n", $times)]
);

//COMMIT
self::$db->query('COMMIT');
$this->updateCacheObject();
Expand Down Expand Up @@ -1169,6 +1217,12 @@ public function cancelRestOfSeason()
MyRadioEmail::sendEmailToUser($u, 'Show Cancelled', $email);
}

MyRadio_AuditLog::log(
AuditLogTypes::SeasonCancelled,
$this->season_id,
['timeslots' => explode('\r\n', $timeslot_str)]
);

$r = (bool) self::$db->query(
'DELETE FROM schedule.show_season_timeslot WHERE show_season_id=$1 AND start_time >= NOW()',
[$this->getID()]
Expand Down
Loading