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
71 changes: 71 additions & 0 deletions api/src/main/java/org/btuk/proxy/api/impl/StatsApiImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.btuk.proxy.api.impl;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.btuk.proxy.api.model.PlayerBaseStats;
import org.btuk.proxy.api.model.TotalBaseStats;
import org.btuk.proxy.database.sql.GlobalSQL;
import org.btuk.proxy.database.sql.PlotSQL;

@Path("/stats")
@Produces(MediaType.APPLICATION_JSON)
public class StatsApiImpl {

private final GlobalSQL globalSQL;
private final PlotSQL plotSQL;

public StatsApiImpl(@Context GlobalSQL globalSQL, @Context PlotSQL plotSQL) {
this.globalSQL = globalSQL;
this.plotSQL = plotSQL;
}

@GET
@Path("/total")
public Response getTotalStats() {
try {
// Fetch total stats from your database layer
org.btuk.proxy.database.dto.TotalBaseStats stats = globalSQL.getTotalBaseStats();
if (stats == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(stats).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}

@GET
@Path("/player")
public Response getPlayerStats(@QueryParam("uuid") String uuid) {
if (uuid == null || uuid.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Player UUID is required")
.build();
}

try {
// Fetch individual player stats from your database layer
org.btuk.proxy.database.dto.PlayerBaseStats stats = globalSQL.getPlayerBaseStats(uuid);
if (stats == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
int reviews = plotSQL.getPlayerTotalReviews(uuid);
PlayerBaseStats combinedStats = new PlayerBaseStats();

combinedStats.buildings(stats.buildings());
combinedStats.tplls(stats.tplls());
combinedStats.messagesSent(stats.messagesSent());
combinedStats.reviewsCompleted(reviews);
combinedStats.timePlayed(stats.timePlayed());

return Response.ok(combinedStats).build();
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
}
9 changes: 7 additions & 2 deletions api/src/main/java/org/btuk/proxy/api/server/ProxyApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import lombok.extern.java.Log;
import org.btuk.proxy.api.impl.BuildingsApiImpl;
import org.btuk.proxy.api.impl.PlayerApiImpl;
import org.btuk.proxy.api.impl.StatsApiImpl;
import org.btuk.proxy.api.impl.StatusApiImpl;
import org.btuk.proxy.core.chat.ChatManager;
import org.btuk.proxy.database.sql.GlobalSQL;

import org.btuk.proxy.database.sql.PlotSQL;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.internal.inject.AbstractBinder;
Expand All @@ -22,13 +24,15 @@ public class ProxyApi {
private final int port;
private final GlobalSQL globalSQL;
private final ChatManager chatManager;
private final PlotSQL plotSQL;


public ProxyApi(boolean enabled, int port, GlobalSQL globalSQL, ChatManager chatManager) {
public ProxyApi(boolean enabled, int port, GlobalSQL globalSQL, ChatManager chatManager, PlotSQL plotSQL) {
this.enabled = enabled;
this.port = port;
this.globalSQL = globalSQL;
this.chatManager = chatManager;
this.plotSQL = plotSQL;
}

public void start() {
Expand Down Expand Up @@ -56,13 +60,14 @@ public void start() {
protected void configure() {
bind(globalSQL).to(GlobalSQL.class);
bind(chatManager).to(ChatManager.class);
bind(plotSQL).to(PlotSQL.class);
}
});

// 3. Register the implementation CLASSES (or scan the package)
rc.register(StatusApiImpl.class);
rc.register(PlayerApiImpl.class);
rc.register(BuildingsApiImpl.class);
rc.register(StatsApiImpl.class);

// ResourceConfig rc = new ResourceConfig()
// .property("jersey.config.server.wadl.disableWadl", true)
Expand Down
60 changes: 59 additions & 1 deletion api/src/main/resources/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,41 @@ paths:
schema:
$ref: '#/components/schemas/BuildingGridResponse'

/stats/total:
get:
tags:
- Stats
summary: Get overall base statistics
operationId: getTotalStats
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TotalBaseStats'

/stats/player:
get:
tags:
- Stats
summary: Get individual player base statistics
operationId: getPlayerStats
parameters:
- name: uuid
in: query
required: false
description: The UUID of the player.
schema:
type: string
format: uuid
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/PlayerBaseStats'
components:
schemas:
Status:
Expand Down Expand Up @@ -367,4 +402,27 @@ components:
cells:
type: array
items:
$ref: '#/components/schemas/GridCell'
$ref: '#/components/schemas/GridCell'
TotalBaseStats:
type: object
properties:
buildings:
type: integer
recentBuildings:
type: integer
previousRecentBuildings:
type: integer

PlayerBaseStats:
type: object
properties:
buildings:
type: integer
tplls:
type: integer
timePlayed:
type: integer
messagesSent:
type: integer
reviewsCompleted:
type: integer
2 changes: 1 addition & 1 deletion app/src/main/java/org.btuk.proxy.app/ProxyController.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public void start(ChatHandler chatHandler, Scheduler scheduler, CoreServerManage
new ReviewStatus(config, globalSQL, plotSQL, regionSQL, discord, scheduler);

this.discord.addJDAEventListeners(chatManager, coreUserManager, tabManager, plotSQL);
this.proxyApi = new ProxyApi(config.getBoolean("api.enabled"), config.getInt("api.port"), globalSQL, chatManager);
this.proxyApi = new ProxyApi(config.getBoolean("api.enabled"), config.getInt("api.port"), globalSQL, chatManager,plotSQL);
serverManager.initOnlineServers();

socketInitializer.accept(new ProxySocketHandler(chatManager, discord, userManager, serverManager));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.btuk.proxy.database.dto;

public record PlayerBaseStats(
int buildings,
int tplls,
int timePlayed,
int messagesSent
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.btuk.proxy.database.dto;

public record TotalBaseStats(
int buildings,
int recentBuildings,
int previousRecentBuildings
) {}
71 changes: 71 additions & 0 deletions database/src/main/java/org/btuk/proxy/database/sql/GlobalSQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.btuk.proxy.database.dto.BuildingDTO;
import org.btuk.proxy.database.dto.GridCellDTO;
import org.btuk.proxy.database.dto.PlayerDTO;
import org.btuk.proxy.database.dto.TotalBaseStats;
import org.btuk.proxy.database.dto.PlayerBaseStats;

import javax.sql.DataSource;
import java.sql.Connection;
Expand Down Expand Up @@ -436,4 +438,73 @@ public List<GridCellDTO> getBuildingGridCounts(double minLat, double maxLat, dou
}
return cells;
}

/**
* Retrieves overall base statistics.
* Counts total buildings and recent buildings added within the last 30 days.
*/
public TotalBaseStats getTotalBaseStats() {
final String sql = """
SELECT
(SELECT COUNT(*) FROM buildings) AS total_buildings,
(SELECT COUNT(*) FROM buildings WHERE time_added >= NOW() - INTERVAL 30 DAY) AS recent_buildings,
(SELECT COUNT(*) FROM buildings WHERE time_added < NOW() - INTERVAL 30 DAY AND time_added >= NOW() - INTERVAL 60 DAY) AS previous_buildings;
""";

try (Connection conn = conn();
PreparedStatement statement = conn.prepareStatement(sql);
ResultSet results = statement.executeQuery()) {

if (results.next()) {
return new TotalBaseStats(
results.getInt("total_buildings"),
results.getInt("recent_buildings"),
results.getInt("previous_buildings")
);
}
} catch (SQLException e) {
log.severe("Failed to fetch total base stats: " + e.getMessage());
}
return new TotalBaseStats(0, 0,0);
}

/**
* Retrieves aggregated statistics for a specific player by UUID.
*/
public PlayerBaseStats getPlayerBaseStats(String uuid) {
if (uuid == null || uuid.isBlank()) {
log.warning("getPlayerBaseStats called with null or empty uuid");
return null;
}

final String sql = """
SELECT
(SELECT COUNT(*) FROM buildings WHERE player_id = ?) AS buildings,
(SELECT COALESCE(SUM(tpll), 0) FROM statistics WHERE uuid = ?) AS tplls,
(SELECT COALESCE(SUM(playtime), 0) FROM statistics WHERE uuid = ?) AS time_played,
(SELECT COALESCE(SUM(messages), 0) FROM statistics WHERE uuid = ?) AS messages_sent;
""";

try (Connection conn = conn(); PreparedStatement statement = conn.prepareStatement(sql)) {
statement.setString(1, uuid);
statement.setString(2, uuid);
statement.setString(3, uuid);
statement.setString(4, uuid);

try (ResultSet results = statement.executeQuery()) {
if (results.next()) {
return new PlayerBaseStats(
results.getInt("buildings"),
results.getInt("tplls"),
results.getInt("time_played"),
results.getInt("messages_sent")
);
}
}
} catch (SQLException e) {
log.severe("Failed to fetch player base stats for " + uuid + ": " + e.getMessage());
}
return null;
}

}
22 changes: 22 additions & 0 deletions database/src/main/java/org/btuk/proxy/database/sql/PlotSQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,26 @@ private int[][] getOldPlotCorners(int[][] corners, int plotID) {
return corners;
}
}

public int getPlayerTotalReviews(String uuid){
if (uuid == null) {
log.warning("getPlayerTotalReviews called with null uuid");
return 0;
}

final String sql = "SELECT COUNT(*) FROM plot_review WHERE reviewer = ?;";

try (Connection conn = conn(); PreparedStatement statement = conn.prepareStatement(sql)) {
statement.setString(1, uuid);

try (ResultSet results = statement.executeQuery()) {
if (results.next()) {
return results.getInt(1);
}
}
} catch (SQLException e) {
log.severe("An error occurred while fetching total reviews for " + uuid + ": " + e.getMessage());
}
return 0;
}
}