From 4bb5fe3c58efc4bd14f50257ebe7de43a4b63792 Mon Sep 17 00:00:00 2001 From: Brian Foley Date: Sun, 10 May 2026 16:08:43 +0000 Subject: [PATCH] Add api/v1/stats/user/:username/rounds endpoints Code borrowed from theme.inc show_tally_specific_stats --- api/dp-openapi.yaml | 71 +++++++++++++++++++++++++++++++++++++++++-- api/v1.inc | 3 ++ api/v1_stats.inc | 53 ++++++++++++++++++++++++++++++++ api/v1_validators.inc | 9 ++++++ 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/api/dp-openapi.yaml b/api/dp-openapi.yaml index ebe7c2e168..eb0b148f83 100644 --- a/api/dp-openapi.yaml +++ b/api/dp-openapi.yaml @@ -1361,7 +1361,7 @@ paths: schema: type: object additionalProperties: - $ref: '#/components/schemas/round_stats' + $ref: '#/components/schemas/site_round_stats' /stats/site/rounds/{roundid}: get: @@ -1381,7 +1381,55 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/round_stats' + $ref: '#/components/schemas/site_round_stats' + + /stats/user/{username}/rounds: + get: + tags: + - stats + description: Gets the user statistics for all rounds + parameters: + - name: username + in: path + description: Username + required: true + schema: + type: string + responses: + 200: + description: Requested round statistics + content: + application/json: + schema: + type: object + additionalProperties: + $ref: '#/components/schemas/user_round_stats' + + /stats/user/{username}/rounds/{roundid}: + get: + tags: + - stats + description: Gets the user statistics for a round + parameters: + - name: username + in: path + description: Username + required: true + schema: + type: string + - name: roundid + in: path + description: Round ID + required: true + schema: + type: string + responses: + 200: + description: Requested round statistics + content: + application/json: + schema: + $ref: '#/components/schemas/user_round_stats' /documents: get: @@ -1898,7 +1946,7 @@ components: description: Project statistics by state/stage Keys are state/stage ID, values are the number of projects in that state - round_stats: + site_round_stats: type: object properties: today_goal: @@ -1924,6 +1972,23 @@ components: readOnly: true description: Statistics for a round + user_round_stats: + type: object + properties: + pages_today: + type: integer + readOnly: true + pages_yesterday: + type: integer + readOnly: true + pages_total: + type: integer + readOnly: true + current_rank: + type: integer + readOnly: true + description: User statistics for a round + wordlist: type: array items: diff --git a/api/v1.inc b/api/v1.inc index 74ee146e80..0ebf395113 100644 --- a/api/v1.inc +++ b/api/v1.inc @@ -21,6 +21,7 @@ $router->add_validator(":pageroundid", "validate_page_round"); $router->add_validator(":queueid", "validate_release_queue"); $router->add_validator(":document", "validate_document"); $router->add_validator(":storagekey", "validate_storage_key"); +$router->add_validator(":username", "validate_username"); // Add routes $router->add_route("GET", "v1/documents", "api_v1_documents"); @@ -69,6 +70,8 @@ $router->add_route("GET", "v1/stats/site/projects/stages", "api_v1_stats_site_pr $router->add_route("GET", "v1/stats/site/projects/states", "api_v1_stats_site_projects_states"); $router->add_route("GET", "v1/stats/site/rounds", "api_v1_stats_site_rounds"); $router->add_route("GET", "v1/stats/site/rounds/:roundid", "api_v1_stats_site_round"); +$router->add_route("GET", "v1/stats/user/:username/rounds", "api_v1_stats_user_rounds"); +$router->add_route("GET", "v1/stats/user/:username/rounds/:roundid", "api_v1_stats_user_round"); $router->add_route("GET", "v1/storage/:storagekey", "api_v1_storage"); $router->add_route("PUT", "v1/storage/:storagekey", "api_v1_storage"); diff --git a/api/v1_stats.inc b/api/v1_stats.inc index 981d4ce15e..c0452388e5 100644 --- a/api/v1_stats.inc +++ b/api/v1_stats.inc @@ -160,3 +160,56 @@ function api_v1_stats_site_round(string $method, array $data, array $query_param return render_round_stats($round->id); } + +//--------------------------------------------------------------------------- +// stats/user/:username/rounds + +function render_round_user_stats(User $user, Round $round) +{ + if (!user_is_a_sitemanager()) { + if ($user->username != User::current_username()) { + throw new UnauthorizedError("You do not have permission to view stats for another user"); + } + } + + $tallyboard = new TallyBoard($round->id, 'U'); + + $current_tally = $tallyboard->get_current_tally($user->u_id); + $current_rank = $tallyboard->get_rank($user->u_id); + + $snapshot_info = $tallyboard->get_info_from_latest_snapshot($user->u_id); + $yesterday_page_delta = $snapshot_info['tally_delta']; + $today_page_delta = $current_tally - $snapshot_info['tally_value']; + + return [ + "pages_today" => (int)$today_page_delta, + "pages_yesterday" => (int)$yesterday_page_delta, + "pages_total" => (int)$current_tally, + "current_rank" => (int)$current_rank, + ]; +} + +/** @param array $query_params */ +function api_v1_stats_user_rounds(string $method, array $data, array $query_params) +{ + $user = $data[":username"]; + + $return = []; + foreach (Rounds::get_all() as $round) { + $return[$round->id] = render_round_user_stats($user, $round); + } + return $return; +} + + +//--------------------------------------------------------------------------- +// stats/user/:username/rounds/:roundid +// +/** @param array $query_params */ +function api_v1_stats_user_round(string $method, array $data, array $query_params) +{ + $user = $data[":username"]; + $round = $data[":roundid"]; + + return render_round_user_stats($user, $round); +} diff --git a/api/v1_validators.inc b/api/v1_validators.inc index 7fd840b053..92281654a9 100644 --- a/api/v1_validators.inc +++ b/api/v1_validators.inc @@ -98,3 +98,12 @@ function validate_storage_key(string $storage_key, array $data): string } return $storage_key; } + +function validate_username(string $username): User +{ + try { + return new User($username); + } catch (NonexistentUserException | NonuniqueUserException $exception) { + throw new NotFoundError($exception->getMessage(), $exception->getCode()); + } +}