Skip to content

Commit

Permalink
[AB-xxx] adding server info controller
Browse files Browse the repository at this point in the history
adding banner
changing api endpoints
  • Loading branch information
Sheldan committed Mar 26, 2024
1 parent efa059f commit ab7166a
Show file tree
Hide file tree
Showing 19 changed files with 187 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.List;

@RestController
@RequestMapping(value = "/experience")
@RequestMapping(value = "/experience/v1/")
public class ExperienceConfigController {

@Autowired
Expand All @@ -32,7 +32,7 @@ public class ExperienceConfigController {
@Autowired
private GuildService guildService;

@GetMapping(value = "/leaderboard/{serverId}/config", produces = "application/json")
@GetMapping(value = "/leaderboards/{serverId}/config", produces = "application/json")
public ExperienceConfig getLeaderboard(@PathVariable("serverId") Long serverId) {
AServer server = serverManagementService.loadServer(serverId);
List<LevelRole> levelRoles = experienceRoleService.loadLevelRoleConfigForServer(server);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/experience")
@RequestMapping(value = "/experience/v1")
public class LeaderboardController {

@Autowired
Expand All @@ -37,7 +37,7 @@ public class LeaderboardController {
@Autowired
private GuildService guildService;

@GetMapping(value = "/leaderboard/{serverId}", produces = "application/json")
@GetMapping(value = "/leaderboards/{serverId}", produces = "application/json")
public Page<UserExperienceDisplay> getLeaderboard(@PathVariable("serverId") Long serverId,
@PageableDefault(value = 25, page = 0)
@SortDefault(sort = "experience", direction = Sort.Direction.DESC)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.sheldan.abstracto.core.api;

import dev.sheldan.abstracto.core.exception.GuildNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
@Slf4j
public class ExceptionHandlerConfig extends ResponseEntityExceptionHandler {

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(GuildNotFoundException.class)
protected ResponseEntity<String> handleResourceNotFound(GuildNotFoundException ex){
log.warn("Server not found.", ex);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("Server not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dev.sheldan.abstracto.core.api;

import dev.sheldan.abstracto.core.models.api.GuildDisplay;
import dev.sheldan.abstracto.core.service.GuildService;
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
import net.dv8tion.jda.api.entities.Guild;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/servers/v1/")
public class ServerController {

@Autowired
private ServerManagementService serverManagementService;

@Autowired
private GuildService guildService;

@GetMapping(value = "/{serverId}/info", produces = "application/json")
public GuildDisplay getLeaderboard(@PathVariable("serverId") Long serverId) {
serverManagementService.loadServer(serverId); // only used for verification if it exists in the db
Guild guild = guildService.getGuildById(serverId);
return GuildDisplay
.builder()
.name(guild.getName())
.id(guild.getIdLong())
.bannerUrl(guild.getBannerUrl())
.iconUrl(guild.getIconUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dev.sheldan.abstracto.core.models.api;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class GuildDisplay {
private Long id;
private String name;
private String iconUrl;
private String bannerUrl;
}
5 changes: 5 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ services:
context: python/components/image-gen
dockerfile: docker/Dockerfile
image: ${REGISTRY_PREFIX}abstracto-rest-api-image-gen:${VERSION:-latest}
core_api:
build:
context: python/components/core
dockerfile: docker/Dockerfile
image: ${REGISTRY_PREFIX}abstracto-rest-api-core:${VERSION:-latest}
experience_api:
build:
context: python/components/experience-tracking
Expand Down
3 changes: 3 additions & 0 deletions python/components/core/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine:3.19.0
ADD resources /python/resources
ADD python /python
16 changes: 16 additions & 0 deletions python/components/core/python/endpoints/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __main__ import app

import requests
import logging
import os

backend_host = os.getenv('BACKEND_HOST')
backend_port = os.getenv('BACKEND_PORT')

server_url = f'http://{backend_host}:{backend_port}/servers/v1'

@app.route('/servers/v1/<serverId>/info')
def get_server_info(serverId):
server = requests.get(f'{server_url}/{serverId}/info')
logging.info(f'returning server info')
return server.text, server.status_code
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
from __main__ import app

from flask import request, render_template
from flask_cors import cross_origin
import requests
import logging
import os

backend_host = os.getenv('BACKEND_HOST')
backend_port = os.getenv('BACKEND_PORT')

leaderboard_url = f'http://{backend_host}:{backend_port}/experience/leaderboard'
leaderboard_url = f'http://{backend_host}:{backend_port}/experience/v1/leaderboards'

@cross_origin()
@app.route('/experience/api/leaderboard/<serverId>')
@app.route('/experience/v1/leaderboards/<serverId>')
def get_leaderboard(serverId):
page = int(request.args.get('page', 0, type=int))
size = int(request.args.get('size', 25, type=int))
leaderboard = requests.get(f'{leaderboard_url}/{serverId}?page={page}&size={size}')
logging.info(f'returning leaderboard for server')
return leaderboard.text
return leaderboard.text, leaderboard.status_code

@cross_origin()
@app.route('/experience/api/leaderboard/<serverId>/config')
@app.route('/experience/v1/leaderboards/<serverId>/config')
def get_experience_config(serverId):
leaderboard = requests.get(f'{leaderboard_url}/{serverId}/config')
logging.info(f'returning experience config for server')
return leaderboard.text
return leaderboard.text, leaderboard.status_code


@app.route('/experience/leaderboard/<serverId>')
@app.route('/experience/leaderboards/<serverId>')
def render_index(serverId):
return render_template('experience/leaderboard/index.html', serverId=serverId)
return render_template('experience/leaderboards/index.html', serverId=serverId)
Empty file.
2 changes: 1 addition & 1 deletion ui/experience-tracking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "experience-tracking",
"version": "0.1.0",
"private": true,
"homepage": "/experience/leaderboard",
"homepage": "/experience/leaderboards",
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
Expand Down
2 changes: 1 addition & 1 deletion ui/experience-tracking/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
name="description"
content="Leaderboard for experience"
/>
<script>window.serverId=1n</script>
<script>window.serverId={{ serverId }}n</script>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Experience leaderboard</title>
</head>
Expand Down
2 changes: 1 addition & 1 deletion ui/experience-tracking/src/components/ErrorDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export const ErrorDisplay = () => {
return (
<>
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">Failed to show leaderboard</h1>
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">Failed to load leaderboard</h1>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ export const ExperienceConfigDisplay = ({serverId}: { serverId: bigint }) => {
const [hasError, setError] = useState(false)
async function loadConfig() {
try {
const configResponse = await fetch(`experience/api/leaderboard/${serverId}/config`)
const configResponse = await fetch(`/experience/v1/leaderboards/${serverId}/config`)
let configObj: ExperienceConfig = await configResponse.json();
const roles = configObj.roles;
setRoles(roles)
} catch (error) {
console.log(error)
setError(true)
}
}
Expand All @@ -40,7 +41,7 @@ export const ExperienceConfigDisplay = ({serverId}: { serverId: bigint }) => {
</thead>
<tbody>
{roles.map(role =>
<tr key={role.role.id} className="bg-white border-b bg-gray-800 border-gray-700">
<tr key={role.role.id} className="border-b bg-gray-800 border-gray-700">
<td className="px-6 py-4">
<RoleDisplay role={role.role}/>
</td>
Expand Down
105 changes: 66 additions & 39 deletions ui/experience-tracking/src/components/Leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {LeaderboardEntry} from "./LeaderboardEntry";
import {useEffect, useState} from "react";
import {ExperienceMember} from "../data/leaderboard";
import {ExperienceMember, GuildInfo} from "../data/leaderboard";
import {ExperienceConfigDisplay} from "./ExperienceConfigDisplay";
import {ErrorDisplay} from "./ErrorDisplay";

Expand All @@ -13,72 +13,99 @@ export function Leaderboard({serverId}: { serverId: bigint }) {
const [pageCount, setPageCount] = useState(0)
const [hasMore, setHasMore] = useState(true)
const [hasError, setError] = useState(false)
const [guildInfo, setGuildInfo] = useState<GuildInfo>({} as GuildInfo)

async function loadLeaderboard(page: number, size: number) {
try {
const leaderboardResponse = await fetch(`experience/api/leaderboard/${serverId}?page=${page}&size=${size}`)
const leaderboardResponse = await fetch(`/experience/v1/leaderboards/${serverId}?page=${page}&size=${size}`)
const leaderboardJson = await leaderboardResponse.json()
const loadedMembers: Array<ExperienceMember> = leaderboardJson.content;
setMemberCount(memberCount + loadedMembers.length)
setHasMore(!leaderboardJson.last)
setPageCount(page)
setMembers(members.concat(loadedMembers))
} catch (error) {
console.log(error)
setError(true)
}
}

async function loadGuildInfo() {
try {
const guildInfoResponse = await fetch(`/servers/v1/${serverId}/info`)
const guildInfoJson: GuildInfo= await guildInfoResponse.json()
setGuildInfo(guildInfoJson)
} catch (error) {
console.log(error)
}
}

useEffect(()=> {
if(memberCount === 0) {
loadLeaderboard(0, pageSize)
}
loadGuildInfo()
},[])

function loadMore() {
loadLeaderboard(pageCount + 1, pageSize)
}

let loadMoreButton = <button className="w-full bg-gray-500 hover:bg-gray-700 text-white" onClick={loadMore}>Load more</button>;
return (
<>
{!hasError ?
<div className="flex">
<div className="text-sm text-left w-3/4">
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">Leaderboard</h1>
<table className="w-full text-gray-400">
<thead
className="text-xs uppercase bg-gray-700 text-gray-400">
<tr>
<th scope="col" className="px-6 py-3 w-1/3">
Member
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Experience
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Messages
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Level
</th>
<th scope="col" className="px-6 py-3 w-1/3 text-center">
Role
</th>
</tr>
</thead>
<tbody>
{members.map(member => <LeaderboardEntry key={member.id} member={member}/>)}
</tbody>
</table>
{hasMore ? loadMoreButton : ''}
</div>
<div className="w-1/4 px-3">
<ExperienceConfigDisplay serverId={serverId}/>
</div>
</div>
: <ErrorDisplay/>}
</>
<>
<div className="relative font-[sans-serif] before:absolute before:w-full before:h-full before:inset-0 before:bg-black before:opacity-50 before:z-10 h-48">
{guildInfo.bannerUrl !== null ? <img src={guildInfo.bannerUrl + "?size=4096"}
alt="Banner Image"
className="absolute inset-0 w-full h-full object-cover"/> : ''}
<div
className="min-h-[150px] relative z-50 h-full max-w-6xl mx-auto flex flex-row justify-center items-center text-center text-white p-6">
{guildInfo.iconUrl !== null ? <img
src={guildInfo.iconUrl + "?size=512"}
alt="Icon image"
className="w-24"/>
: ''}
<h1 className="text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">{'Leaderboard for ' + guildInfo.name}</h1>
</div>

</div>
<div className="flex">
<div className="text-sm text-left w-3/4 ">
<table className="w-full text-gray-400">
<thead
className="text-xs uppercase bg-gray-700 text-gray-400">
<tr>
<th scope="col" className="px-6 py-3 w-1/3">
Member
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Experience
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Messages
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Level
</th>
<th scope="col" className="px-6 py-3 w-1/3 text-center">
Role
</th>
</tr>
</thead>
<tbody>
{members.map(member => <LeaderboardEntry key={member.id} member={member}/>)}
</tbody>
</table>
{hasMore ? loadMoreButton : ''}
</div>
<div className="w-1/4 px-3">
<ExperienceConfigDisplay serverId={serverId}/>
</div>
</div>
</>
: <ErrorDisplay/>}
</>
);
}

2 changes: 1 addition & 1 deletion ui/experience-tracking/src/components/LeaderboardEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const LeaderboardEntry = ({member}: { member: ExperienceMember }) => {
const nameColor = userHasRole ? createStyle(member.role!) : ''
let memberDisplay = memberExists ? <>
<img alt={member.member!.name} src={member.member!.avatarUrl}
className="object-contain h-24 w-24 rounded-full"/>
className="object-contain h-16 w-16 rounded-full"/>
<span className="align-middle" style={{color: nameColor}}>{member.member!.name}</span>
</> : <>{member.id}</>;
return (
Expand Down
Loading

0 comments on commit ab7166a

Please sign in to comment.