banlist/follow page stuff
This commit is contained in:
parent
d291b19a6a
commit
889d2c40e4
@ -73,8 +73,14 @@ class SecurityConfig {
|
|||||||
.authorizeHttpRequests { auth ->
|
.authorizeHttpRequests { auth ->
|
||||||
auth
|
auth
|
||||||
.requestMatchers("/user-queue").authenticated()
|
.requestMatchers("/user-queue").authenticated()
|
||||||
|
.requestMatchers( "/follows", "/follows/**").authenticated()
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
}
|
}
|
||||||
|
.exceptionHandling {
|
||||||
|
it.authenticationEntryPoint { _, response, _ ->
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
||||||
|
}
|
||||||
|
}
|
||||||
.oauth2Login { oauthLogin ->
|
.oauth2Login { oauthLogin ->
|
||||||
oauthLogin.successHandler(CustomAuthenticationSuccessHandler())
|
oauthLogin.successHandler(CustomAuthenticationSuccessHandler())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
package com.nisemoe.nise.controller
|
package com.nisemoe.nise.controller
|
||||||
|
|
||||||
import com.nisemoe.generated.tables.references.USERS
|
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.Valid
|
||||||
import jakarta.validation.constraints.Size
|
import jakarta.validation.constraints.Min
|
||||||
import org.jooq.DSLContext
|
import org.jooq.DSLContext
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
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.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
@ -20,15 +15,84 @@ class BanlistController(
|
|||||||
private val dslContext: DSLContext
|
private val dslContext: DSLContext
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class BanStatisticsResponse(
|
companion object {
|
||||||
val totalUsersBanned: Int
|
|
||||||
|
const val MAX_BANLIST_ENTRIES_PER_PAGE = 100
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BanlistRequest(
|
||||||
|
@field:Min(1)
|
||||||
|
val page: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@GetMapping("banlist/statistics")
|
data class BanlistResponse(
|
||||||
fun getBanStatistics(): BanStatisticsResponse {
|
val pagination: BanlistPagination,
|
||||||
val totalUsersBanned = dslContext.fetchCount(USERS, USERS.IS_BANNED.eq(true))
|
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")
|
@GetMapping("follows")
|
||||||
fun getFollowsBanStatus(): ResponseEntity<FollowsBanStatusResponse> {
|
fun getFollowsBanStatus(): ResponseEntity<FollowsBanStatusResponse> {
|
||||||
if(!authService.isLoggedIn()) {
|
|
||||||
return ResponseEntity.status(401).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val follows = dslContext.select(
|
val follows = dslContext.select(
|
||||||
USERS.USER_ID,
|
USERS.USER_ID,
|
||||||
USERS.USERNAME,
|
USERS.USERNAME,
|
||||||
@ -70,10 +66,6 @@ class FollowsController(
|
|||||||
|
|
||||||
@GetMapping("follows/{followsUserId}")
|
@GetMapping("follows/{followsUserId}")
|
||||||
fun getFollowsBanStatusByUserId(@PathVariable followsUserId: Long): ResponseEntity<FollowsBanStatusEntry> {
|
fun getFollowsBanStatusByUserId(@PathVariable followsUserId: Long): ResponseEntity<FollowsBanStatusEntry> {
|
||||||
if(!authService.isLoggedIn()) {
|
|
||||||
return ResponseEntity.status(401).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val userId = authService.getCurrentUser().userId
|
val userId = authService.getCurrentUser().userId
|
||||||
|
|
||||||
val follows = dslContext.select(
|
val follows = dslContext.select(
|
||||||
@ -109,10 +101,6 @@ class FollowsController(
|
|||||||
|
|
||||||
@PutMapping("follows")
|
@PutMapping("follows")
|
||||||
fun updateFollowsBanStatus(@RequestBody @Valid request: UpdateFollowsBanStatusRequest): ResponseEntity<Void> {
|
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
|
// 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) {
|
if(dslContext.fetchCount(USER_FOLLOWS, USER_FOLLOWS.USER_ID.eq(authService.getCurrentUser().userId)) >= MAX_FOLLOWS_PER_USER) {
|
||||||
return ResponseEntity.status(400).build()
|
return ResponseEntity.status(400).build()
|
||||||
@ -138,10 +126,6 @@ class FollowsController(
|
|||||||
|
|
||||||
@DeleteMapping("follows")
|
@DeleteMapping("follows")
|
||||||
fun deleteFollowsBanStatus(@RequestBody @Valid request: DeleteFollowsBanStatusRequest): ResponseEntity<Void> {
|
fun deleteFollowsBanStatus(@RequestBody @Valid request: DeleteFollowsBanStatusRequest): ResponseEntity<Void> {
|
||||||
if(!authService.isLoggedIn()) {
|
|
||||||
return ResponseEntity.status(401).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
val userId = authService.getCurrentUser().userId
|
val userId = authService.getCurrentUser().userId
|
||||||
|
|
||||||
for(userIdToUnban in request.userIds) {
|
for(userIdToUnban in request.userIds) {
|
||||||
|
|||||||
@ -344,6 +344,7 @@ class ImportScores(
|
|||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
if(isBanned == true) {
|
if(isBanned == true) {
|
||||||
|
this.messagingTemplate.convertAndSend("/topic/banlist", userId)
|
||||||
dslContext.update(SCORES)
|
dslContext.update(SCORES)
|
||||||
.set(SCORES.IS_BANNED, true)
|
.set(SCORES.IS_BANNED, true)
|
||||||
.where(SCORES.USER_ID.eq(userId))
|
.where(SCORES.USER_ID.eq(userId))
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import org.jooq.DSLContext
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.annotation.Profile
|
import org.springframework.context.annotation.Profile
|
||||||
|
import org.springframework.messaging.simp.SimpMessagingTemplate
|
||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
@ -21,6 +22,7 @@ class ImportUsers(
|
|||||||
private val userService: UserService,
|
private val userService: UserService,
|
||||||
private val discordService: DiscordService,
|
private val discordService: DiscordService,
|
||||||
private val osuApi: OsuApi,
|
private val osuApi: OsuApi,
|
||||||
|
private val messagingTemplate: SimpMessagingTemplate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(javaClass)
|
private val logger = LoggerFactory.getLogger(javaClass)
|
||||||
@ -103,6 +105,7 @@ class ImportUsers(
|
|||||||
Thread.sleep(SLEEP_AFTER_API_CALL)
|
Thread.sleep(SLEEP_AFTER_API_CALL)
|
||||||
if (isUserBanned == true) {
|
if (isUserBanned == true) {
|
||||||
this.logger.warn("User $missingId is banned")
|
this.logger.warn("User $missingId is banned")
|
||||||
|
this.messagingTemplate.convertAndSend("/topic/banlist", missingId)
|
||||||
dslContext.update(SCORES)
|
dslContext.update(SCORES)
|
||||||
.set(SCORES.IS_BANNED, true)
|
.set(SCORES.IS_BANNED, true)
|
||||||
.where(SCORES.USER_ID.eq(missingId))
|
.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 {SearchComponent} from "./search/search.component";
|
||||||
import {ContributeComponent} from "./contribute/contribute.component";
|
import {ContributeComponent} from "./contribute/contribute.component";
|
||||||
import {BanlistComponent} from "./banlist/banlist.component";
|
import {BanlistComponent} from "./banlist/banlist.component";
|
||||||
|
import {FollowsComponent} from "./follows/follows.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'},
|
{path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'},
|
||||||
@ -24,8 +25,10 @@ const routes: Routes = [
|
|||||||
|
|
||||||
{path: 'p/:replay1Id/:replay2Id', component: ViewReplayPairComponent},
|
{path: 'p/:replay1Id/:replay2Id', component: ViewReplayPairComponent},
|
||||||
|
|
||||||
|
{path: 'follows', component: FollowsComponent, title: '/follows/'},
|
||||||
{path: 'banlist', component: BanlistComponent, title: '/ban/'},
|
{path: 'banlist', component: BanlistComponent, title: '/ban/'},
|
||||||
{path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'},
|
{path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'},
|
||||||
|
|
||||||
{path: '**', component: HomeComponent, title: '/nise.moe/'},
|
{path: '**', component: HomeComponent, title: '/nise.moe/'},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,71 @@
|
|||||||
<div class="main term mb-2">
|
<div class="main term mb-2">
|
||||||
<div class="fade-stuff">
|
<div class="fade-stuff">
|
||||||
<h1 class="mb-4"># follow-list</h1>
|
<h1 class="mb-4"># recent bans</h1>
|
||||||
<table *ngIf="this.follows">
|
<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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2">Username</th>
|
<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>Last check</th>
|
||||||
|
<th>Approximate ban date</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let user of this.follows.follows">
|
<tr *ngFor="let user of this.banlist.users">
|
||||||
<td>
|
<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>
|
||||||
<td>
|
<td>
|
||||||
<a [routerLink]="['/u', user.username]">
|
<a [routerLink]="['/u', user.username]">
|
||||||
{{ user.username }}
|
{{ user.username }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ user.isBanned }}</td>
|
<td>
|
||||||
<td>{{ calculateTimeAgo(user.lastUpdate) }}</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>
|
||||||
</td>
|
</td>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -1,26 +1,35 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {environment} from "../../environments/environment";
|
import {environment} from "../../environments/environment";
|
||||||
import {JsonPipe, NgForOf, NgIf} from "@angular/common";
|
import {DatePipe, DecimalPipe, JsonPipe, NgForOf, NgIf} from "@angular/common";
|
||||||
import {calculateTimeAgo} from "../format";
|
import {calculateTimeAgo, formatDuration} from "../format";
|
||||||
import {RouterLink} from "@angular/router";
|
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 {
|
interface BanlistResponse {
|
||||||
totalUsersBanned: number;
|
pagination: BanlistPagination;
|
||||||
|
users: BanlistEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FollowsBanStatusResponse {
|
interface BanlistPagination {
|
||||||
follows: FollowsBanStatusEntry[];
|
currentPage: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalResults: number;
|
||||||
|
totalPages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FollowsBanStatusEntry {
|
interface BanlistEntry {
|
||||||
userId: number;
|
userId: number;
|
||||||
username: string;
|
username: string;
|
||||||
isBanned: boolean;
|
secondsPlayed?: number;
|
||||||
lastUpdate: string;
|
pp?: number;
|
||||||
|
rank?: number;
|
||||||
|
isBanned?: boolean;
|
||||||
|
approximateBanTime?: string;
|
||||||
|
lastUpdate?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-banlist',
|
selector: 'app-banlist',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@ -28,34 +37,36 @@ interface FollowsBanStatusEntry {
|
|||||||
JsonPipe,
|
JsonPipe,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgIf,
|
NgIf,
|
||||||
RouterLink
|
RouterLink,
|
||||||
|
DatePipe,
|
||||||
|
DecimalPipe,
|
||||||
|
CuteProgressbarComponent,
|
||||||
|
CuteLoadingComponent
|
||||||
],
|
],
|
||||||
templateUrl: './banlist.component.html',
|
templateUrl: './banlist.component.html',
|
||||||
styleUrl: './banlist.component.css'
|
styleUrl: './banlist.component.css'
|
||||||
})
|
})
|
||||||
export class BanlistComponent implements OnInit {
|
export class BanlistComponent implements OnInit {
|
||||||
|
|
||||||
banStatistics: BanStatisticsResponse | null = null
|
isLoading = true;
|
||||||
follows: FollowsBanStatusResponse | null = null;
|
banlist: BanlistResponse | null = null;
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) { }
|
constructor(private httpClient: HttpClient) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getBanStatistics();
|
this.getBanlist();
|
||||||
this.getFollows();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBanStatistics() {
|
getBanlist(): void {
|
||||||
this.httpClient.get<BanStatisticsResponse>(`${environment.apiUrl}/banlist/statistics`).subscribe(response => {
|
const body = {
|
||||||
this.banStatistics = response;
|
page: 1
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.httpClient.post<BanlistResponse>(`${environment.apiUrl}/banlist`, body).subscribe(response => {
|
||||||
getFollows(): void {
|
this.banlist = response;
|
||||||
this.httpClient.get<FollowsBanStatusResponse>(`${environment.apiUrl}/follows`).subscribe(response => {
|
this.isLoading = false;
|
||||||
this.follows = response;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly calculateTimeAgo = calculateTimeAgo;
|
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 {
|
.score-entry:hover {
|
||||||
background-color: rgba(179, 184, 195, 0.15);
|
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');
|
src: url(ia-quattro-700-normal.woff2) format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: rgba(227, 232, 255, 0.1);
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background-color: #151515;
|
background-color: #151515;
|
||||||
|
|
||||||
@ -190,7 +196,7 @@ a.btn-success:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
padding: 5px;
|
padding: 8px;
|
||||||
border: 1px dotted #b3b8c3;
|
border: 1px dotted #b3b8c3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user