Add staff playtime feature, including backend services, API endpoint, and frontend integration.
WIP
This commit is contained in:
parent
2be79c180a
commit
8b0d2f9203
|
|
@ -51,6 +51,8 @@ public class SecurityConfig {
|
||||||
.requestMatchers("/api/form/**").authenticated()
|
.requestMatchers("/api/form/**").authenticated()
|
||||||
.requestMatchers("/api/login/getUsername").authenticated()
|
.requestMatchers("/api/login/getUsername").authenticated()
|
||||||
.requestMatchers("/api/mail/**").authenticated()
|
.requestMatchers("/api/mail/**").authenticated()
|
||||||
|
.requestMatchers("/api/site/vote").authenticated()
|
||||||
|
.requestMatchers("/api/site/get-staff-playtime/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.requestMatchers("/api/head_mod/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
.requestMatchers("/api/head_mod/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.requestMatchers("/api/particles/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
.requestMatchers("/api/particles/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.requestMatchers("/api/files/save/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
.requestMatchers("/api/files/save/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,21 @@ package com.alttd.altitudeweb.controllers.site;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.api.SiteApi;
|
import com.alttd.altitudeweb.api.SiteApi;
|
||||||
import com.alttd.altitudeweb.controllers.data_from_auth.AuthenticatedUuid;
|
import com.alttd.altitudeweb.controllers.data_from_auth.AuthenticatedUuid;
|
||||||
|
import com.alttd.altitudeweb.model.StaffPlaytimeDto;
|
||||||
|
import com.alttd.altitudeweb.model.StaffPlaytimeListDto;
|
||||||
import com.alttd.altitudeweb.model.VoteDataDto;
|
import com.alttd.altitudeweb.model.VoteDataDto;
|
||||||
import com.alttd.altitudeweb.model.VoteStatsDto;
|
import com.alttd.altitudeweb.model.VoteStatsDto;
|
||||||
import com.alttd.altitudeweb.services.limits.RateLimit;
|
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||||
|
import com.alttd.altitudeweb.services.site.StaffPtService;
|
||||||
import com.alttd.altitudeweb.services.site.VoteService;
|
import com.alttd.altitudeweb.services.site.VoteService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
@ -23,6 +29,18 @@ public class SiteController implements SiteApi {
|
||||||
|
|
||||||
private final VoteService voteService;
|
private final VoteService voteService;
|
||||||
private final AuthenticatedUuid authenticatedUuid;
|
private final AuthenticatedUuid authenticatedUuid;
|
||||||
|
private final StaffPtService staffPtService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<StaffPlaytimeListDto> getStaffPlaytime(OffsetDateTime from, OffsetDateTime to) {
|
||||||
|
Optional<List<StaffPlaytimeDto>> staffPlaytimeDto = staffPtService.getStaffPlaytime(from.toInstant(), to.toInstant());
|
||||||
|
if (staffPlaytimeDto.isEmpty()) {
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
StaffPlaytimeListDto staffPlaytimeListDto = new StaffPlaytimeListDto();
|
||||||
|
staffPlaytimeListDto.addAll(staffPlaytimeDto.get());
|
||||||
|
return ResponseEntity.ok(staffPlaytimeListDto);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<VoteDataDto> getVoteStats() {
|
public ResponseEntity<VoteDataDto> getVoteStats() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.alttd.altitudeweb.mappers;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.database.luckperms.Player;
|
||||||
|
import com.alttd.altitudeweb.database.proxyplaytime.StaffPt;
|
||||||
|
import com.alttd.altitudeweb.model.StaffPlaytimeDto;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public final class StaffPtToStaffPlaytimeMapper {
|
||||||
|
private record PlaytimeInfo(long totalPlaytime, long lastPlayed) {}
|
||||||
|
|
||||||
|
public List<StaffPlaytimeDto> map(List<StaffPt> sessions, List<Player> staffMembers, long from, long to) {
|
||||||
|
Map<UUID, PlaytimeInfo> playtimeData = getUuidPlaytimeInfoMap(sessions, from, to);
|
||||||
|
|
||||||
|
List<StaffPlaytimeDto> results = new ArrayList<>(playtimeData.size());
|
||||||
|
for (Map.Entry<UUID, PlaytimeInfo> entry : playtimeData.entrySet()) {
|
||||||
|
long lastPlayedMillis = entry.getValue().lastPlayed() == Long.MIN_VALUE ? 0L : entry.getValue().lastPlayed();
|
||||||
|
StaffPlaytimeDto dto = new StaffPlaytimeDto();
|
||||||
|
dto.setStaffMember(staffMembers.stream()
|
||||||
|
.filter(player -> player.uuid().equals(entry.getKey()))
|
||||||
|
.map(Player::username)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(entry.getKey().toString())
|
||||||
|
);
|
||||||
|
dto.setLastPlayed(OffsetDateTime.from(Instant.ofEpochMilli(lastPlayedMillis)));
|
||||||
|
dto.setPlaytime((int) TimeUnit.MILLISECONDS.toMinutes(entry.getValue().totalPlaytime()));
|
||||||
|
results.add(dto);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<UUID, PlaytimeInfo> getUuidPlaytimeInfoMap(List<StaffPt> sessions, long from, long to) {
|
||||||
|
Map<UUID, PlaytimeInfo> playtimeData = new HashMap<>();
|
||||||
|
for (StaffPt session : sessions) {
|
||||||
|
long overlapStart = Math.max(session.sessionStart(), from);
|
||||||
|
long overlapEnd = Math.min(session.sessionEnd(), to);
|
||||||
|
if (overlapEnd <= overlapStart) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaytimeInfo info = playtimeData.getOrDefault(session.uuid(), new PlaytimeInfo(0L, Long.MIN_VALUE));
|
||||||
|
long totalPlaytime = info.totalPlaytime() + (overlapEnd - overlapStart);
|
||||||
|
long lastPlayed = Math.max(info.lastPlayed(), overlapEnd);
|
||||||
|
playtimeData.put(session.uuid(), new PlaytimeInfo(totalPlaytime, lastPlayed));
|
||||||
|
}
|
||||||
|
return playtimeData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.alttd.altitudeweb.services.site;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.luckperms.Player;
|
||||||
|
import com.alttd.altitudeweb.database.luckperms.TeamMemberMapper;
|
||||||
|
import com.alttd.altitudeweb.database.proxyplaytime.StaffPlaytimeMapper;
|
||||||
|
import com.alttd.altitudeweb.database.proxyplaytime.StaffPt;
|
||||||
|
import com.alttd.altitudeweb.mappers.StaffPtToStaffPlaytimeMapper;
|
||||||
|
import com.alttd.altitudeweb.model.StaffPlaytimeDto;
|
||||||
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StaffPtService {
|
||||||
|
private final static String STAFF_GROUPS = "group.admin, group.developer, group.headmod, group.manager, group.moderator, group.owner, group.trainee";
|
||||||
|
private final StaffPtToStaffPlaytimeMapper staffPtToStaffPlaytimeMapper;
|
||||||
|
|
||||||
|
public Optional<List<StaffPlaytimeDto>> getStaffPlaytime(Instant from, Instant to) {
|
||||||
|
CompletableFuture<List<Player>> staffMembersFuture = new CompletableFuture<>();
|
||||||
|
CompletableFuture<List<StaffPt>> staffPlaytimeFuture = new CompletableFuture<>();
|
||||||
|
Connection.getConnection(Databases.LUCK_PERMS)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
log.debug("Loading staff members");
|
||||||
|
try {
|
||||||
|
List<Player> staffMemberList = sqlSession.getMapper(TeamMemberMapper.class)
|
||||||
|
.getTeamMembersOfGroupList(STAFF_GROUPS);
|
||||||
|
staffMembersFuture.complete(staffMemberList);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load staff members", e);
|
||||||
|
staffMembersFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
List<Player> staffMembers = staffMembersFuture.join().stream()
|
||||||
|
.collect(Collectors.collectingAndThen(
|
||||||
|
Collectors.toMap(Player::uuid, player -> player, (player1, player2) -> player1),
|
||||||
|
m -> new ArrayList<>(m.values())));
|
||||||
|
Connection.getConnection(Databases.PROXY_PLAYTIME)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
String staffUUIDs = staffMembers.stream()
|
||||||
|
.map(Player::uuid)
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
log.debug("Loading staff playtime for group");
|
||||||
|
try {
|
||||||
|
List<StaffPt> sessionsDuring = sqlSession.getMapper(StaffPlaytimeMapper.class)
|
||||||
|
.getSessionsDuring(from.toEpochMilli(), to.toEpochMilli(), staffUUIDs);
|
||||||
|
staffPlaytimeFuture.complete(sessionsDuring);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load staff playtime", e);
|
||||||
|
staffPlaytimeFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
List<StaffPt> join = staffPlaytimeFuture.join();
|
||||||
|
|
||||||
|
return Optional.of(staffPtToStaffPlaytimeMapper.map(join, staffMembers, from.toEpochMilli(), to.toEpochMilli()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ public enum Databases {
|
||||||
LUCK_PERMS("luckperms"),
|
LUCK_PERMS("luckperms"),
|
||||||
LITE_BANS("litebans"),
|
LITE_BANS("litebans"),
|
||||||
DISCORD("discordLink"),
|
DISCORD("discordLink"),
|
||||||
|
PROXY_PLAYTIME("proxyplaytime"),
|
||||||
VOTING_PLUGIN("votingplugin");
|
VOTING_PLUGIN("votingplugin");
|
||||||
|
|
||||||
private final String internalName;
|
private final String internalName;
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,19 @@ public interface TeamMemberMapper {
|
||||||
AND world = 'global'
|
AND world = 'global'
|
||||||
""")
|
""")
|
||||||
List<Player> getTeamMembers(@Param("groupPermission") String groupPermission);
|
List<Player> getTeamMembers(@Param("groupPermission") String groupPermission);
|
||||||
|
|
||||||
|
@ConstructorArgs({
|
||||||
|
@Arg(column = "username", javaType = String.class),
|
||||||
|
@Arg(column = "uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class)
|
||||||
|
})
|
||||||
|
@Select("""
|
||||||
|
SELECT players.username, players.uuid
|
||||||
|
FROM luckperms_user_permissions AS permissions
|
||||||
|
INNER JOIN luckperms_players AS players ON players.uuid = permissions.uuid
|
||||||
|
WHERE permission IN (${groupPermissions})
|
||||||
|
AND server = 'global'
|
||||||
|
AND world = 'global'
|
||||||
|
""")
|
||||||
|
List<Player> getTeamMembersOfGroupList(@Param("groupPermissions") String groupPermissions);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.alttd.altitudeweb.database.proxyplaytime;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.type_handler.UUIDTypeHandler;
|
||||||
|
import org.apache.ibatis.annotations.Arg;
|
||||||
|
import org.apache.ibatis.annotations.ConstructorArgs;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface StaffPlaytimeMapper {
|
||||||
|
@ConstructorArgs({
|
||||||
|
@Arg(column = "uuid", javaType = UUID.class, typeHandler = UUIDTypeHandler.class),
|
||||||
|
@Arg(column = "serverName", javaType = String.class),
|
||||||
|
@Arg(column = "sessionStart", javaType = long.class),
|
||||||
|
@Arg(column = "sessionEnd", javaType = long.class)
|
||||||
|
})
|
||||||
|
@Select("""
|
||||||
|
SELECT uuid,
|
||||||
|
server_name AS serverName,
|
||||||
|
session_start AS sessionStart,
|
||||||
|
session_end AS sessionEnd
|
||||||
|
FROM sessions
|
||||||
|
WHERE session_end > #{from}
|
||||||
|
AND session_start < #{to}
|
||||||
|
AND uuid IN (${staffUUIDs})
|
||||||
|
ORDER BY uuid, session_start
|
||||||
|
""")
|
||||||
|
List<StaffPt> getSessionsDuring(@Param("from") long from,
|
||||||
|
@Param("to") long to,
|
||||||
|
@Param("staffUUIDs") String staffUUIDs);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.alttd.altitudeweb.database.proxyplaytime;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record StaffPt(UUID uuid, String serverName, long sessionStart, long sessionEnd) {
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ public class Connection {
|
||||||
InitializeWebDb.init();
|
InitializeWebDb.init();
|
||||||
InitializeLiteBans.init();
|
InitializeLiteBans.init();
|
||||||
InitializeLuckPerms.init();
|
InitializeLuckPerms.init();
|
||||||
|
InitializeProxyPlaytime.init();
|
||||||
InitializeDiscord.init();
|
InitializeDiscord.init();
|
||||||
InitializeVotingPlugin.init();
|
InitializeVotingPlugin.init();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.alttd.altitudeweb.setup;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.proxyplaytime.StaffPlaytimeMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class InitializeProxyPlaytime {
|
||||||
|
|
||||||
|
protected static void init() {
|
||||||
|
log.info("Initializing ProxyPlaytime");
|
||||||
|
Connection.getConnection(Databases.PROXY_PLAYTIME, (configuration) -> {
|
||||||
|
configuration.addMapper(StaffPlaytimeMapper.class);
|
||||||
|
}).join();
|
||||||
|
log.debug("Initialized ProxyPlaytime");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,14 @@ export const routes: Routes = [
|
||||||
requiredAuthorizations: ['SCOPE_head_mod']
|
requiredAuthorizations: ['SCOPE_head_mod']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'staff-pt',
|
||||||
|
loadComponent: () => import('./pages/particles/particles.component').then(m => m.ParticlesComponent),
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
data: {
|
||||||
|
requiredAuthorizations: ['SCOPE_head_mod']
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'map',
|
path: 'map',
|
||||||
loadComponent: () => import('./pages/features/map/map.component').then(m => m.MapComponent)
|
loadComponent: () => import('./pages/features/map/map.component').then(m => m.MapComponent)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<p>staff-pt works!</p>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {Component, inject, OnInit, signal} from '@angular/core';
|
||||||
|
import {SiteService, StaffPlaytime} from '@api';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-staff-pt',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './staff-pt.component.html',
|
||||||
|
styleUrl: './staff-pt.component.scss'
|
||||||
|
})
|
||||||
|
export class StaffPtComponent implements OnInit {
|
||||||
|
siteService = inject(SiteService);
|
||||||
|
staffPt = signal<StaffPlaytime[]>([])
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const firstDayOfWeek = new Date();
|
||||||
|
firstDayOfWeek.setDate(firstDayOfWeek.getDate() - firstDayOfWeek.getDay());
|
||||||
|
firstDayOfWeek.setHours(0, 0, 0, 0);
|
||||||
|
const lastDayOfWeek = new Date(firstDayOfWeek);
|
||||||
|
lastDayOfWeek.setDate(firstDayOfWeek.getDate() + 6);
|
||||||
|
lastDayOfWeek.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
this.loadStaffData(firstDayOfWeek, lastDayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStaffData(from: Date, to: Date) {
|
||||||
|
this.siteService.getStaffPlaytime(from.toISOString(), to.toISOString())
|
||||||
|
.subscribe({
|
||||||
|
next: data => {
|
||||||
|
this.staffPt.set(data);
|
||||||
|
},
|
||||||
|
error: err => console.error('Error getting staff playtime:', err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -144,6 +144,7 @@
|
||||||
<ul class="dropdown">
|
<ul class="dropdown">
|
||||||
@if (hasAccess([PermissionClaim.HEAD_MOD])) {
|
@if (hasAccess([PermissionClaim.HEAD_MOD])) {
|
||||||
<li class="nav_li"><a class="nav_link2" [routerLink]="['/particles']">Particles</a></li>
|
<li class="nav_li"><a class="nav_link2" [routerLink]="['/particles']">Particles</a></li>
|
||||||
|
<li class="nav_li"><a class="nav_link2" [routerLink]="['/staff-pt']">StaffPlaytime</a></li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,11 @@ tasks.register< GenerateTask>("generateJavaApi") {
|
||||||
typeMappings.put("OffsetDateTime", "Instant")
|
typeMappings.put("OffsetDateTime", "Instant")
|
||||||
importMappings.put("java.time.OffsetDateTime", "java.time.Instant")
|
importMappings.put("java.time.OffsetDateTime", "java.time.Instant")
|
||||||
modelNameSuffix.set("Dto")
|
modelNameSuffix.set("Dto")
|
||||||
|
|
||||||
|
// Make generator use Java 8 time types and map date-time -> Instant
|
||||||
|
additionalProperties.set(mapOf("dateLibrary" to "java8"))
|
||||||
|
typeMappings.set(mapOf("date-time" to "Instant"))
|
||||||
|
importMappings.set(mapOf("Instant" to "java.time.Instant"))
|
||||||
generateModelTests.set(false)
|
generateModelTests.set(false)
|
||||||
generateModelDocumentation.set(false)
|
generateModelDocumentation.set(false)
|
||||||
generateApiTests.set(false)
|
generateApiTests.set(false)
|
||||||
|
|
|
||||||
|
|
@ -93,3 +93,5 @@ paths:
|
||||||
$ref: './schemas/forms/mail/mail.yml#/GetEmails'
|
$ref: './schemas/forms/mail/mail.yml#/GetEmails'
|
||||||
/api/site/vote:
|
/api/site/vote:
|
||||||
$ref: './schemas/site/vote.yml#/VoteStats'
|
$ref: './schemas/site/vote.yml#/VoteStats'
|
||||||
|
/api/site/get-staff-playtime/{from}/{to}:
|
||||||
|
$ref: './schemas/site/staff_pt.yml#/GetStaffPlaytime'
|
||||||
|
|
|
||||||
53
open_api/src/main/resources/schemas/site/staff_pt.yml
Normal file
53
open_api/src/main/resources/schemas/site/staff_pt.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
GetStaffPlaytime:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- site
|
||||||
|
summary: Get staff playtime for a specified duration
|
||||||
|
description: Get staff playtime for all staff members for a specified duration
|
||||||
|
operationId: getStaffPlaytime
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/From'
|
||||||
|
- $ref: '#/components/parameters/To'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Staff playtime retrieved
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/StaffPlaytimeList'
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
From:
|
||||||
|
name: from
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
example: 2025-01-01T00:00:00.000Z
|
||||||
|
To:
|
||||||
|
name: to
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
example: 2025-01-07T23:59:59.999Z
|
||||||
|
schemas:
|
||||||
|
StaffPlaytimeList:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/StaffPlaytime'
|
||||||
|
StaffPlaytime:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
staff_member:
|
||||||
|
type: string
|
||||||
|
description: The name of the staff member
|
||||||
|
playtime:
|
||||||
|
type: integer
|
||||||
|
description: Total playtime for the specified duration in minutes
|
||||||
|
last_played:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Last played timestamp
|
||||||
Loading…
Reference in New Issue
Block a user