Compare commits

..

2 Commits

Author SHA1 Message Date
Teriuihi 905373093c Update rate limiting headers and adjust time unit for limits
Added `Access-Control-Expose-Headers` to rate limit responses to expose retry-related headers for easier accessibility on the client side. Changed the rate limit time unit in `HistoryApiController` from seconds to minutes for more reasonable throttling.
2025-04-18 19:32:34 +02:00
Teriuihi 21f2b3e4a5 Refactor pagination logic and add error handling in history
Introduced `updatePageSize` for better page size management and added checks to prevent rapid page changes. Enhanced error handling in `history.component` with retry logic on failure. Implemented `RemoveTrailingPeriodPipe` for cleaner UI formatting.
2025-04-18 19:32:23 +02:00
7 changed files with 63 additions and 9 deletions

View File

@ -15,10 +15,11 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
@RateLimit(limit = 30, timeValue = 1, timeUnit = java.util.concurrent.TimeUnit.SECONDS)
@RateLimit(limit = 30, timeValue = 1, timeUnit = TimeUnit.MINUTES)
public class HistoryApiController implements HistoryApi {
@Override

View File

@ -76,6 +76,7 @@ public class RateLimitAspect {
.header("X-Rate-Limit-Limit", String.valueOf(limit))
.header("X-Rate-Limit-Remaining", "0")
.header("Retry-After", String.valueOf(nextResetTime.getSeconds()))
.header("Access-Control-Expose-Headers", "Retry-After, X-Rate-Limit-Limit, X-Rate-Limit-Remaining")
.body(String.format("Rate limit exceeded. Try again in %d seconds.", nextResetTime.getSeconds()));
}
}

View File

@ -48,7 +48,7 @@
</div>
<div class="historyTable">
<app-history [userType]="userType" [punishmentType]="punishmentType"
[page]="page" [searchTerm]="finalSearchTerm" (pageChange)="pageSize = $event">
[page]="page" [searchTerm]="finalSearchTerm" (pageChange)="updatePageSize($event)">
</app-history>
</div>
<div class="changePageButtons">

View File

@ -40,13 +40,14 @@ export class BansComponent implements OnInit {
public finalSearchTerm: string = '';
public searchTerm: string = '';
public filteredNames: string[] = [];
public pageSize = 0;
public pageSize = 10;
public historyCount: HistoryCount = {
bans: 0,
mutes: 0,
kicks: 0,
warnings: 0
}
private actualPage: number = 0;
ngOnInit() {
this.historyApi.getUserNames(this.userType, this.punishmentType).subscribe(names => {
@ -108,17 +109,21 @@ export class BansComponent implements OnInit {
if (this.page === this.getMaxPage()) {
return;
}
this.setPage(++this.page)
this.setPage(this.page + 1)
}
public previousPage() {
if (this.page === 0) {
return;
}
this.setPage(--this.page)
this.setPage(this.page - 1)
}
public setPage(page: number) {
if (this.actualPage !== this.page) {
console.warn('Changing page too quickly, the previous page change has not loaded yet');
return;
}
this.pushState();
this.page = page
}
@ -159,4 +164,13 @@ export class BansComponent implements OnInit {
}
return this.page !== compare;
}
public updatePageSize(pageSize: number) {
if (pageSize < 0) {
return;
} else {
this.pageSize = pageSize;
this.actualPage = this.page;
}
}
}

View File

@ -34,7 +34,7 @@
<span>{{ entry.punishedBy }}</span>
</div>
</td>
<td class="historyReason">{{ entry.reason }}</td>
<td class="historyReason">{{ entry.reason | removeTrailingPeriod }}</td>
<td class="historyDate">{{ getPunishmentTime(entry) }}</td>
<td class="historyDate">{{ getExpiredTime(entry) }}</td>
</tr>

View File

@ -1,15 +1,18 @@
import {Component, EventEmitter, Input, OnChanges, OnInit, Output} from '@angular/core';
import {BASE_PATH, HistoryService, PunishmentHistoryInner} from '../../../api';
import {map, Observable, shareReplay} from 'rxjs';
import {catchError, map, Observable, shareReplay} from 'rxjs';
import {NgForOf, NgIf, NgOptimizedImage} from '@angular/common';
import {CookieService} from 'ngx-cookie-service';
import {RemoveTrailingPeriodPipe} from '../../util/RemoveTrailingPeriodPipe';
import {HttpErrorResponse} from '@angular/common/http';
@Component({
selector: 'app-history',
imports: [
NgIf,
NgForOf,
NgOptimizedImage
NgOptimizedImage,
RemoveTrailingPeriodPipe
],
templateUrl: './history.component.html',
styleUrl: './history.component.scss',
@ -36,7 +39,6 @@ export class HistoryComponent implements OnInit, OnChanges {
ngOnChanges(): void {
this.reloadHistory();
this.pageChange.emit(this.history.length);
}
ngOnInit(): void {
@ -57,6 +59,27 @@ export class HistoryComponent implements OnInit, OnChanges {
historyObservable.pipe(
map(history => {
this.history = history;
this.pageChange.emit(this.history.length);
}),
catchError(err => {
this.pageChange.emit(-1);
let retrySeconds = 5;
if (err instanceof HttpErrorResponse) {
const headers = err.headers;
const retryAfterHeader = headers.get('Retry-After');
console.warn(err.error);
if (retryAfterHeader) {
const retryAfter = parseInt(retryAfterHeader || '0', 10);
if (!isNaN(retryAfter) && retryAfter > 0) {
retrySeconds = retryAfter + 1;
}
}
} else {
console.error(err);
}
setTimeout(() => this.reloadHistory(), retrySeconds * 1000);
return this.history;
}),
shareReplay(1)
).subscribe();

View File

@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'removeTrailingPeriod',
standalone: true
})
export class RemoveTrailingPeriodPipe implements PipeTransform {
transform(value: string): string {
if (!value) {
return value;
}
return value.endsWith('.') ? value.slice(0, -1) : value;
}
}