-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
302 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
* Zookeeper Online | ||
* | ||
* @author Jim Mason <[email protected]> | ||
* @copyright Copyright (C) 1997-2024 Jim Mason <[email protected]> | ||
* @copyright Copyright (C) 1997-2025 Jim Mason <[email protected]> | ||
* @link https://zookeeper.ibinx.com/ | ||
* @license GPL-3.0 | ||
* | ||
|
@@ -52,4 +52,16 @@ function insertReview($tag, $private, $airname, $review, $user); | |
function updateReview($tag, $private, $airname, $review, $user); | ||
function deleteReview($tag, $user); | ||
function setExportId($tag, $user, $exportId); | ||
function getReviewShelf(); | ||
/** | ||
* update review shelf status | ||
* | ||
* @param int $tag target album | ||
* @param ?string $user check album out to user or null (see below) | ||
* @return ?array old status or null if none | ||
* | ||
* If $user is null and album is pending approval, it is moved | ||
* to the library; otherwise, it is returned to the review shelf. | ||
*/ | ||
function updateReviewShelf(int $tag, ?string $user = null): ?array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
* Zookeeper Online | ||
* | ||
* @author Jim Mason <[email protected]> | ||
* @copyright Copyright (C) 1997-2024 Jim Mason <[email protected]> | ||
* @copyright Copyright (C) 1997-2025 Jim Mason <[email protected]> | ||
* @link https://zookeeper.ibinx.com/ | ||
* @license GPL-3.0 | ||
* | ||
|
@@ -29,6 +29,18 @@ | |
* Music review operations | ||
*/ | ||
class ReviewImpl extends DBO implements IReview { | ||
// these codes are defined in ILibrary::LOCATIONS | ||
private const ALBUM_AWAITING_REVIEW = 'E'; // Review Shelf | ||
private const ALBUM_IN_REVIEW = 'F'; // Out for Review | ||
private const ALBUM_REVIEWED = 'H'; // Pending Approval | ||
private const ALBUM_LIBRARY = 'L'; // Library | ||
|
||
private const REVIEW_SHELF = [ | ||
self::ALBUM_AWAITING_REVIEW, | ||
self::ALBUM_IN_REVIEW, | ||
self::ALBUM_REVIEWED | ||
]; | ||
|
||
private function getRecentSubquery($user = "", $weeks = 0, $loggedIn = 0) { | ||
// IMPORTANT: If columns change, revisit getRecentReviews below | ||
$query = "SELECT a.airname, r.user, DATE_FORMAT(r.created, GET_FORMAT(DATE, 'ISO')) reviewed, r.id as rid, u.realname, r.tag, v.category, v.album, v.artist, v.iscoll FROM reviews r "; | ||
|
@@ -191,8 +203,9 @@ protected function syncHashtags(int $tag, string $user, ?string $review = null) | |
} | ||
|
||
public function insertReview($tag, $private, $airname, $review, $user) { | ||
// we must do this first, as caller depends on lastInsertId from INSERT | ||
// we must do these first as caller depends on lastInsertId from INSERT | ||
$this->syncHashtags($tag, $user, $private ? null : $review); | ||
$prev = $this->updateReviewShelf($tag, null, self::ALBUM_REVIEWED); | ||
|
||
$query = "INSERT INTO reviews " . | ||
"(tag, user, created, private, review, airname) VALUES (" . | ||
|
@@ -209,10 +222,14 @@ public function insertReview($tag, $private, $airname, $review, $user) { | |
$stmt->bindValue(5, $airname); | ||
$count = $stmt->execute() ? $stmt->rowCount() : 0; | ||
|
||
// back out hashtags on failure | ||
if(!$count) | ||
// back out hashtags and review shelf on failure | ||
if(!$count) { | ||
$this->syncHashtags($tag, $user); | ||
|
||
if($prev) | ||
$this->updateReviewShelf($tag, $prev['user'], $prev['status']); | ||
} | ||
|
||
return $count; | ||
} | ||
|
||
|
@@ -247,8 +264,10 @@ public function deleteReview($tag, $user) { | |
$count = $stmt->execute() ? $stmt->rowCount() : 0; | ||
|
||
// delete any associated hashtags | ||
if($count) | ||
if($count) { | ||
$this->syncHashtags($tag, $user); | ||
$this->updateReviewShelf($tag, null, self::ALBUM_AWAITING_REVIEW); | ||
} | ||
|
||
return $count; | ||
} | ||
|
@@ -262,4 +281,37 @@ public function setExportId($tag, $user, $exportId) { | |
$stmt->bindValue(3, $user); | ||
return $stmt->execute()?$stmt->rowCount():0; | ||
} | ||
|
||
public function getReviewShelf() { | ||
$n = count(self::REVIEW_SHELF); | ||
$query = "SELECT a.*, u.realname FROM albumvol a LEFT JOIN users u ON a.bin = u.name WHERE location IN ( ?" . str_repeat(', ?', $n - 1) . " ) ORDER BY artist, album"; | ||
$stmt = $this->prepare($query); | ||
foreach(self::REVIEW_SHELF as $status) | ||
$stmt->bindValue($n--, $status); | ||
|
||
return $stmt->executeAndFetchAll(); | ||
} | ||
|
||
function updateReviewShelf(int $tag, ?string $user = null, ?string $status = null): ?array { | ||
$query = "SELECT location status, bin user FROM albumvol WHERE tag = ?"; | ||
$stmt = $this->prepare($query); | ||
$stmt->bindValue(1, $tag); | ||
$result = $stmt->executeAndFetch() ?: null; | ||
|
||
// if album is not in a review status, nothing to do | ||
$ostatus = $result['status'] ?? ''; | ||
if(!in_array($ostatus, self::REVIEW_SHELF)) | ||
return null; | ||
|
||
$location = $ostatus == self::ALBUM_REVIEWED ? | ||
self::ALBUM_LIBRARY : self::ALBUM_AWAITING_REVIEW; | ||
|
||
$query = "UPDATE albumvol SET location = ?, bin = ?, updated = NOW() WHERE tag = ?"; | ||
$stmt = $this->prepare($query); | ||
$stmt->bindValue(1, $status ?? | ||
($user ? self::ALBUM_IN_REVIEW : $location)); | ||
$stmt->bindValue(2, $user); | ||
$stmt->bindValue(3, $tag); | ||
return $stmt->execute() ? $result : null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
* Zookeeper Online | ||
* | ||
* @author Jim Mason <[email protected]> | ||
* @copyright Copyright (C) 1997-2024 Jim Mason <[email protected]> | ||
* @copyright Copyright (C) 1997-2025 Jim Mason <[email protected]> | ||
* @link https://zookeeper.ibinx.com/ | ||
* @license GPL-3.0 | ||
* | ||
|
@@ -55,6 +55,8 @@ class Reviews extends MenuItem { | |
[ "a", "viewDJ", "By DJ", "reviewsByDJ" ], | ||
[ "a", "viewHashtag", "Trending", "viewTrending" ], | ||
[ "a", "trendingData", 0, "getTrendingData" ], | ||
[ "u", "viewReviewShelf", "Review Shelf", "viewReviewShelf" ], | ||
[ "u", "updateReviewShelf", 0, "updateReviewShelf" ], | ||
]; | ||
|
||
private $subaction; | ||
|
@@ -148,6 +150,49 @@ public function getTrendingData() { | |
echo json_encode($data); | ||
} | ||
|
||
public function viewReviewShelf() { | ||
$albums = Engine::api(IReview::class)->getReviewShelf(); | ||
$this->addVar('GENRES', ILibrary::GENRES); | ||
$this->addVar('albums', $albums); | ||
$this->setTemplate("review.shelf.html"); | ||
} | ||
|
||
public function updateReviewShelf() { | ||
$op = $_REQUEST['op'] ?? false; | ||
$tag = $_REQUEST['tag'] ?? false; | ||
if(!$op || !$tag) { | ||
http_response_code(400); // bad request | ||
return; | ||
} | ||
|
||
switch($op) { | ||
case 'claim': | ||
Engine::api(IReview::class)->updateReviewShelf($tag, $this->session->getUser()); | ||
break; | ||
case 'release': | ||
// fall through... | ||
case 'xdtm': | ||
Engine::api(IReview::class)->updateReviewShelf($tag, null); | ||
break; | ||
} | ||
|
||
$album = Engine::api(ILibrary::class)->search(ILibrary::ALBUM_KEY, 0, 1, $tag)[0]; | ||
if($album['bin']) { | ||
$user = Engine::api(ILibrary::class)->search(ILibrary::PASSWD_NAME, 0, 1, $album['bin']); | ||
if(count($user)) | ||
$album['realname'] = $user[0]['realname']; | ||
} | ||
|
||
$this->claimReview($tag, $op); | ||
|
||
$this->addVar('GENRES', ILibrary::GENRES); | ||
$this->addVar('album', $album); | ||
$this->setTemplate("review.shelf.html"); | ||
$html = $this->render('album'); | ||
|
||
echo json_encode(['html' => $html]); | ||
} | ||
|
||
public function viewRecentReviews() { | ||
$isAuthorized = $this->session->isAuth('u'); | ||
$author = $isAuthorized && ($_GET['dj'] ?? '') == 'Me' ? $this->session->getUser() : ''; | ||
|
@@ -164,6 +209,78 @@ public function viewReview() { | |
$this->newEntity(Search::class)->searchByAlbumKey($_REQUEST["tag"]); | ||
} | ||
|
||
private function claimReview($tag, $op) { | ||
// nothing to do if Slack is not configured | ||
$config = Engine::param('slack'); | ||
if($op == 'dtm' || | ||
!$config || !($token = $config['token']) || | ||
!($channel = $config['review_channel'])) { | ||
return; | ||
} | ||
|
||
// find the album | ||
$libAPI = Engine::api(ILibrary::class); | ||
$albums = $libAPI->search(ILibrary::ALBUM_KEY, 0, 1, $tag); | ||
if(!count($albums)) | ||
return; | ||
|
||
$artist = $albums[0]["iscoll"] ? "Various Artists" : $albums[0]["artist"]; | ||
$album = $albums[0]["album"]; | ||
|
||
$user = $libAPI->search(ILibrary::PASSWD_NAME, 0, 1, $this->session->getUser()); | ||
if(!count($user)) | ||
return; | ||
|
||
$reviewer = $user[0]["realname"]; | ||
$unclaim = $op == 'release'; | ||
$action = $unclaim ? "has returned" : "is reviewing"; | ||
$verb = $unclaim ? "returned" : "claimed"; | ||
|
||
$base = Engine::getBaseUrl(); | ||
$title = Engine::param('station_title'); | ||
|
||
// compose the message | ||
$body = [ | ||
'type' => 'section', | ||
'text' => [ | ||
'type' => 'mrkdwn', | ||
'text' => ":lower_left_fountain_pen: $reviewer $action <$base?s=byAlbumKey&n=$tag&action=search|$artist / $album>" | ||
], | ||
]; | ||
|
||
$client = new Client([ | ||
'base_uri' => self::SLACK_BASE, | ||
RequestOptions::HEADERS => [ | ||
'User-Agent' => Engine::UA, | ||
'Authorization' => 'Bearer ' . $token | ||
] | ||
]); | ||
|
||
try { | ||
$options = [ | ||
'channel' => $channel, | ||
'text' => "$reviewer $verb $artist / $album", | ||
'blocks' => [ | ||
$body, | ||
], | ||
]; | ||
|
||
$method = 'chat.postMessage'; | ||
|
||
$response = $client->post($method, [ | ||
RequestOptions::JSON => $options | ||
]); | ||
|
||
// Slack returns success/failure in 'ok' property | ||
$body = $response->getBody()->getContents(); | ||
$json = json_decode($body); | ||
if(!$json->ok) | ||
error_log("postReview: $body"); | ||
} catch(\Exception $e) { | ||
error_log("postReview: " . $e->getMessage()); | ||
} | ||
} | ||
|
||
private function postReview($tag) { | ||
// nothing to do if Slack is not configured | ||
$config = Engine::param('slack'); | ||
|
Oops, something went wrong.