banlist/follow page stuff
This commit is contained in:
parent
d291b19a6a
commit
889d2c40e4
@ -73,8 +73,14 @@ class SecurityConfig {
|
||||
.authorizeHttpRequests { auth ->
|
||||
auth
|
||||
.requestMatchers("/user-queue").authenticated()
|
||||
.requestMatchers( "/follows", "/follows/**").authenticated()
|
||||
.anyRequest().permitAll()
|
||||
}
|
||||
.exceptionHandling {
|
||||
it.authenticationEntryPoint { _, response, _ ->
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
.oauth2Login { oauthLogin ->
|
||||
oauthLogin.successHandler(CustomAuthenticationSuccessHandler())
|
||||
}
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
package com.nisemoe.nise.controller
|
||||
|
||||
import com.nisemoe.generated.tables.references.USERS
|
||||
import com.nisemoe.generated.tables.references.USER_FOLLOWS
|
||||
import com.nisemoe.nise.service.AuthService
|
||||
import jakarta.validation.Valid
|
||||
import jakarta.validation.constraints.Size
|
||||
import jakarta.validation.constraints.Min
|
||||
import org.jooq.DSLContext
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.time.OffsetDateTime
|
||||
@ -20,15 +15,84 @@ class BanlistController(
|
||||
private val dslContext: DSLContext
|
||||
) {
|
||||
|
||||
data class BanStatisticsResponse(
|
||||
val totalUsersBanned: Int
|
||||
companion object {
|
||||
|
||||
const val MAX_BANLIST_ENTRIES_PER_PAGE = 100
|
||||
|
||||
}
|
||||
|
||||
data class BanlistRequest(
|
||||
@field:Min(1)
|
||||
val page: Int,
|
||||
)
|
||||
|
||||
@GetMapping("banlist/statistics")
|
||||
fun getBanStatistics(): BanStatisticsResponse {
|
||||
val totalUsersBanned = dslContext.fetchCount(USERS, USERS.IS_BANNED.eq(true))
|
||||
data class BanlistResponse(
|
||||
val pagination: BanlistPagination,
|
||||
val users: List<BanlistEntry>
|
||||
)
|
||||
|
||||
return BanStatisticsResponse(totalUsersBanned)
|
||||
data class BanlistPagination(
|
||||
val currentPage: Int,
|
||||
val pageSize: Int,
|
||||
val totalResults: Int
|
||||
) {
|
||||
|
||||
val totalPages: Int
|
||||
get() = if (totalResults % pageSize == 0) totalResults / pageSize else totalResults / pageSize + 1
|
||||
|
||||
}
|
||||
|
||||
data class BanlistEntry(
|
||||
val userId: Long,
|
||||
val username: String,
|
||||
val secondsPlayed: Long?,
|
||||
val pp: Double?,
|
||||
val rank: Long?,
|
||||
val isBanned: Boolean?,
|
||||
val approximateBanTime: OffsetDateTime?,
|
||||
val lastUpdate: OffsetDateTime?
|
||||
)
|
||||
|
||||
@PostMapping("banlist")
|
||||
fun banUser(@RequestBody @Valid request: BanlistRequest): ResponseEntity<BanlistResponse> {
|
||||
//
|
||||
val pagination = BanlistPagination(
|
||||
request.page,
|
||||
MAX_BANLIST_ENTRIES_PER_PAGE,
|
||||
dslContext.fetchCount(USERS, USERS.IS_BANNED.eq(true))
|
||||
)
|
||||
|
||||
dslContext.select(
|
||||
USERS.USER_ID,
|
||||
USERS.USERNAME,
|
||||
USERS.SECONDS_PLAYED,
|
||||
USERS.IS_BANNED,
|
||||
USERS.PP_RAW,
|
||||
USERS.RANK,
|
||||
USERS.SYS_LAST_UPDATE,
|
||||
USERS.APPROX_BAN_DATE
|
||||
)
|
||||
.from(USERS)
|
||||
.where(USERS.IS_BANNED.eq(true))
|
||||
.orderBy(USERS.APPROX_BAN_DATE.desc())
|
||||
.limit(MAX_BANLIST_ENTRIES_PER_PAGE)
|
||||
.offset((request.page - 1) * 10)
|
||||
.fetch()
|
||||
.map {
|
||||
BanlistEntry(
|
||||
it[USERS.USER_ID]!!,
|
||||
it[USERS.USERNAME]!!,
|
||||
it[USERS.SECONDS_PLAYED],
|
||||
it[USERS.PP_RAW],
|
||||
it[USERS.RANK],
|
||||
it[USERS.IS_BANNED],
|
||||
it[USERS.APPROX_BAN_DATE],
|
||||
it[USERS.SYS_LAST_UPDATE]
|
||||
)
|
||||
}
|
||||
.let {
|
||||
return ResponseEntity.ok(BanlistResponse(pagination, it))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -40,10 +40,6 @@ class FollowsController(
|
||||
|
||||
@GetMapping("follows")
|
||||
fun getFollowsBanStatus(): ResponseEntity<FollowsBanStatusResponse> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
val follows = dslContext.select(
|
||||
USERS.USER_ID,
|
||||
USERS.USERNAME,
|
||||
@ -70,10 +66,6 @@ class FollowsController(
|
||||
|
||||
@GetMapping("follows/{followsUserId}")
|
||||
fun getFollowsBanStatusByUserId(@PathVariable followsUserId: Long): ResponseEntity<FollowsBanStatusEntry> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
val userId = authService.getCurrentUser().userId
|
||||
|
||||
val follows = dslContext.select(
|
||||
@ -109,10 +101,6 @@ class FollowsController(
|
||||
|
||||
@PutMapping("follows")
|
||||
fun updateFollowsBanStatus(@RequestBody @Valid request: UpdateFollowsBanStatusRequest): ResponseEntity<Void> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
// Check if the user already has MAX_FOLLOWS_PER_USER or more
|
||||
if(dslContext.fetchCount(USER_FOLLOWS, USER_FOLLOWS.USER_ID.eq(authService.getCurrentUser().userId)) >= MAX_FOLLOWS_PER_USER) {
|
||||
return ResponseEntity.status(400).build()
|
||||
@ -138,10 +126,6 @@ class FollowsController(
|
||||
|
||||
@DeleteMapping("follows")
|
||||
fun deleteFollowsBanStatus(@RequestBody @Valid request: DeleteFollowsBanStatusRequest): ResponseEntity<Void> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
val userId = authService.getCurrentUser().userId
|
||||
|
||||
for(userIdToUnban in request.userIds) {
|
||||
|
||||
@ -344,6 +344,7 @@ class ImportScores(
|
||||
.execute()
|
||||
|
||||
if(isBanned == true) {
|
||||
this.messagingTemplate.convertAndSend("/topic/banlist", userId)
|
||||
dslContext.update(SCORES)
|
||||
.set(SCORES.IS_BANNED, true)
|
||||
.where(SCORES.USER_ID.eq(userId))
|
||||
|
||||
@ -10,6 +10,7 @@ import org.jooq.DSLContext
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.OffsetDateTime
|
||||
@ -21,6 +22,7 @@ class ImportUsers(
|
||||
private val userService: UserService,
|
||||
private val discordService: DiscordService,
|
||||
private val osuApi: OsuApi,
|
||||
private val messagingTemplate: SimpMessagingTemplate
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
@ -103,6 +105,7 @@ class ImportUsers(
|
||||
Thread.sleep(SLEEP_AFTER_API_CALL)
|
||||
if (isUserBanned == true) {
|
||||
this.logger.warn("User $missingId is banned")
|
||||
this.messagingTemplate.convertAndSend("/topic/banlist", missingId)
|
||||
dslContext.update(SCORES)
|
||||
.set(SCORES.IS_BANNED, true)
|
||||
.where(SCORES.USER_ID.eq(missingId))
|
||||
|
||||
@ -9,6 +9,7 @@ import {ViewReplayPairComponent} from "./view-replay-pair/view-replay-pair.compo
|
||||
import {SearchComponent} from "./search/search.component";
|
||||
import {ContributeComponent} from "./contribute/contribute.component";
|
||||
import {BanlistComponent} from "./banlist/banlist.component";
|
||||
import {FollowsComponent} from "./follows/follows.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'},
|
||||
@ -24,8 +25,10 @@ const routes: Routes = [
|
||||
|
||||
{path: 'p/:replay1Id/:replay2Id', component: ViewReplayPairComponent},
|
||||
|
||||
{path: 'follows', component: FollowsComponent, title: '/follows/'},
|
||||
{path: 'banlist', component: BanlistComponent, title: '/ban/'},
|
||||
{path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'},
|
||||
|
||||
{path: '**', component: HomeComponent, title: '/nise.moe/'},
|
||||
];
|
||||
|
||||
|
||||
@ -1,27 +1,71 @@
|
||||
<div class="main term mb-2">
|
||||
<div class="fade-stuff">
|
||||
<h1 class="mb-4"># follow-list</h1>
|
||||
<table *ngIf="this.follows">
|
||||
<h1 class="mb-4"># recent bans</h1>
|
||||
<div class="alert mb-2 text-center">
|
||||
<p>
|
||||
just because an user appears on this list, <u>it doesn't mean they were banned for cheating.</u>
|
||||
</p>
|
||||
<p>there are more a multitude of reasons osu!support might close an account.</p>
|
||||
<p>
|
||||
all we do is check if the user exists (with their unique id) and if we get a "hey, it's missing" response (aka the <code>User not found! ;_;</code> message) we mark it as possibly banned.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="this.isLoading">
|
||||
<div class="text-center">
|
||||
<p>Loading <app-cute-loading></app-cute-loading></p>
|
||||
<p>please be patient - the database is working hard!</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #nullTemplate>
|
||||
<code>null</code>
|
||||
</ng-template>
|
||||
<table *ngIf="this.banlist">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Username</th>
|
||||
<th>Is banned?</th>
|
||||
<th>Time played</th>
|
||||
<th>Total PP</th>
|
||||
<th>Rank</th>
|
||||
<th>Last check</th>
|
||||
<th>Approximate ban date</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let user of this.follows.follows">
|
||||
<tr *ngFor="let user of this.banlist.users">
|
||||
<td>
|
||||
<img [src]="'https://a.ppy.sh/' + user.userId" class="avatar" style="width: 16px; min-height: 16px; height: 16px;">
|
||||
<img [src]="'https://a.ppy.sh/' + user.userId" class="avatar" style="width: 16px; min-height: 16px; height: 16px;" loading="lazy">
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/u', user.username]">
|
||||
{{ user.username }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ user.isBanned }}</td>
|
||||
<td>{{ calculateTimeAgo(user.lastUpdate) }}</td>
|
||||
<td>
|
||||
<ng-container *ngIf="user.secondsPlayed else nullTemplate">
|
||||
{{ formatDuration(user.secondsPlayed) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<ng-container *ngIf="user.pp; else nullTemplate">
|
||||
{{ user.pp | number: '1.0-0' }}
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<ng-container *ngIf="user.rank; else nullTemplate">
|
||||
#{{ user.rank | number }}
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<ng-container *ngIf="user.lastUpdate; else nullTemplate">
|
||||
{{ calculateTimeAgo(user.lastUpdate) }}
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
{{ user.approximateBanTime | date: 'medium' }}
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tbody>
|
||||
|
||||
@ -1,26 +1,35 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {JsonPipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {calculateTimeAgo} from "../format";
|
||||
import {DatePipe, DecimalPipe, JsonPipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {calculateTimeAgo, formatDuration} from "../format";
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {CuteProgressbarComponent} from "../../corelib/components/cute-progressbar/cute-progressbar.component";
|
||||
import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-loading.component";
|
||||
|
||||
interface BanStatisticsResponse {
|
||||
totalUsersBanned: number;
|
||||
interface BanlistResponse {
|
||||
pagination: BanlistPagination;
|
||||
users: BanlistEntry[];
|
||||
}
|
||||
|
||||
interface FollowsBanStatusResponse {
|
||||
follows: FollowsBanStatusEntry[];
|
||||
interface BanlistPagination {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
totalResults: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface FollowsBanStatusEntry {
|
||||
interface BanlistEntry {
|
||||
userId: number;
|
||||
username: string;
|
||||
isBanned: boolean;
|
||||
lastUpdate: string;
|
||||
secondsPlayed?: number;
|
||||
pp?: number;
|
||||
rank?: number;
|
||||
isBanned?: boolean;
|
||||
approximateBanTime?: string;
|
||||
lastUpdate?: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-banlist',
|
||||
standalone: true,
|
||||
@ -28,34 +37,36 @@ interface FollowsBanStatusEntry {
|
||||
JsonPipe,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
RouterLink
|
||||
RouterLink,
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
CuteProgressbarComponent,
|
||||
CuteLoadingComponent
|
||||
],
|
||||
templateUrl: './banlist.component.html',
|
||||
styleUrl: './banlist.component.css'
|
||||
})
|
||||
export class BanlistComponent implements OnInit {
|
||||
|
||||
banStatistics: BanStatisticsResponse | null = null
|
||||
follows: FollowsBanStatusResponse | null = null;
|
||||
isLoading = true;
|
||||
banlist: BanlistResponse | null = null;
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getBanStatistics();
|
||||
this.getFollows();
|
||||
this.getBanlist();
|
||||
}
|
||||
|
||||
getBanStatistics() {
|
||||
this.httpClient.get<BanStatisticsResponse>(`${environment.apiUrl}/banlist/statistics`).subscribe(response => {
|
||||
this.banStatistics = response;
|
||||
});
|
||||
getBanlist(): void {
|
||||
const body = {
|
||||
page: 1
|
||||
}
|
||||
|
||||
getFollows(): void {
|
||||
this.httpClient.get<FollowsBanStatusResponse>(`${environment.apiUrl}/follows`).subscribe(response => {
|
||||
this.follows = response;
|
||||
this.httpClient.post<BanlistResponse>(`${environment.apiUrl}/banlist`, body).subscribe(response => {
|
||||
this.banlist = response;
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly calculateTimeAgo = calculateTimeAgo;
|
||||
protected readonly formatDuration = formatDuration;
|
||||
}
|
||||
|
||||
3
nise-frontend/src/app/follows/follows.component.css
Normal file
3
nise-frontend/src/app/follows/follows.component.css
Normal file
@ -0,0 +1,3 @@
|
||||
table td {
|
||||
text-align: center;
|
||||
}
|
||||
31
nise-frontend/src/app/follows/follows.component.html
Normal file
31
nise-frontend/src/app/follows/follows.component.html
Normal file
@ -0,0 +1,31 @@
|
||||
<div class="main term mb-2">
|
||||
<div class="fade-stuff">
|
||||
<h1 class="mb-4"># follow-list</h1>
|
||||
<table *ngIf="this.follows">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Username</th>
|
||||
<th>Is banned?</th>
|
||||
<th>Last check</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let user of this.follows.follows">
|
||||
<td>
|
||||
<img [src]="'https://a.ppy.sh/' + user.userId" class="avatar" style="width: 16px; min-height: 16px; height: 16px;">
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/u', user.username]">
|
||||
{{ user.username }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ user.isBanned }}</td>
|
||||
<td>{{ calculateTimeAgo(user.lastUpdate) }}</td>
|
||||
<td>
|
||||
</td>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
49
nise-frontend/src/app/follows/follows.component.ts
Normal file
49
nise-frontend/src/app/follows/follows.component.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {JsonPipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {calculateTimeAgo} from "../format";
|
||||
import {RouterLink} from "@angular/router";
|
||||
|
||||
interface FollowsBanStatusResponse {
|
||||
follows: FollowsBanStatusEntry[];
|
||||
}
|
||||
|
||||
interface FollowsBanStatusEntry {
|
||||
userId: number;
|
||||
username: string;
|
||||
isBanned: boolean;
|
||||
lastUpdate: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-follows',
|
||||
standalone: true,
|
||||
imports: [
|
||||
JsonPipe,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './follows.component.html',
|
||||
styleUrl: './follows.component.css'
|
||||
})
|
||||
export class FollowsComponent implements OnInit {
|
||||
|
||||
follows: FollowsBanStatusResponse | null = null;
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getFollows();
|
||||
}
|
||||
|
||||
getFollows(): void {
|
||||
this.httpClient.get<FollowsBanStatusResponse>(`${environment.apiUrl}/follows`).subscribe(response => {
|
||||
this.follows = response;
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly calculateTimeAgo = calculateTimeAgo;
|
||||
}
|
||||
@ -19,9 +19,3 @@
|
||||
.score-entry:hover {
|
||||
background-color: rgba(179, 184, 195, 0.15);
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(227, 232, 255, 0.1);
|
||||
padding: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@ -14,6 +14,12 @@
|
||||
src: url(ia-quattro-700-normal.woff2) format('woff2');
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: rgba(227, 232, 255, 0.1);
|
||||
padding: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #151515;
|
||||
|
||||
@ -190,7 +196,7 @@ a.btn-success:hover {
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 5px;
|
||||
padding: 8px;
|
||||
border: 1px dotted #b3b8c3;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user