Compare commits

...

10 Commits

Author SHA1 Message Date
Stedoss
34a05015e2 Add generate report button to user profile 2024-07-21 19:59:06 +01:00
Stedoss
7b59badf26 Merge branch 'user-id-lookup' 2024-07-05 18:59:21 +01:00
Stedoss
eb0c62756b Disable Open in osu!web option on user page when replay_id is 0
Usually, when the `replay_id` is 0 it means the replay was set offline
or not uploaded to osu-web for some reason. Disable the link when this
is the case, as it will just take them to a Not Found page.
2024-07-05 18:21:35 +01:00
Stedoss
0df62d926e Fix null coalescing on user page 2024-07-05 18:04:21 +01:00
Stedoss
7bb7b5c39a Replace username with userId in view-user location state 2024-07-05 00:48:57 +01:00
Stedoss
cfcab7d7cf Use userId for /u/ route links 2024-07-05 00:47:26 +01:00
Stedoss
9719853302 Allow getUserDetails() to lookup on userId when user not found by username query 2024-07-05 00:20:56 +01:00
nise.moe
735f3427c5 Added nise-infra 2024-06-18 13:53:10 +02:00
nise.moe
ff2393b9d5 Removed mentions of konata/mari 2024-06-18 13:44:06 +02:00
nise.moe
c8599103f8 Added some inedexes 2024-06-15 13:13:38 +02:00
20 changed files with 410 additions and 109 deletions

View File

@ -1,94 +0,0 @@
#!/bin/bash
# Initialize variables
commit_message=""
action=""
folder_to_sync=""
external_base_dir="../external-nise.moe"
# Manual argument parsing
while [ "$#" -gt 0 ]; do
case "$1" in
sync|create|configure|push)
action="$1"
folder_to_sync="$2"
shift 2
;;
-m)
commit_message="$2"
shift 2
;;
*)
echo "Invalid argument: $1"
exit 1
;;
esac
done
# Validation
if [ -z "$action" ]; then
echo "Error: Please provide an action (sync or create)."
exit 1
fi
if [ -z "$folder_to_sync" ]; then
echo "Error: Please provide a folder name."
exit 1
fi
target_folder="$external_base_dir/$folder_to_sync"
# Further validation and action handling
if [ "$action" == "sync" ]; then
if [ ! -d "$folder_to_sync" ]; then
echo "Error: Folder '$folder_to_sync' does not exist in the monorepo."
exit 1
fi
mkdir -p "$target_folder"
echo "Synchronizing files from '$folder_to_sync' to '$target_folder'..."
rsync -av --delete --exclude-from='.gitignore' --exclude='.git' "$folder_to_sync/" "$target_folder/"
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
git add .
if [ -z "$commit_message" ]; then
commit_message=$(date +%Y%m%d)
fi
git commit -S -am "$commit_message"
echo "Synchronization complete and changes committed with message: '$commit_message'."
elif [ "$action" == "configure" ]; then
echo "Configuring external folder '$target_folder'..."
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
git config user.email "162507023+nise-moe@users.noreply.github.com"
git config user.name "nise.moe"
git config gpg.format ssh
git config commit.gpgsign true
git config user.signingkey /home/anon/.ssh/nise-moe.pub
elif [ "$action" == "push" ]; then
echo "Pushing changes to remote repository..."
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
git push origin main
echo "Changes pushed to remote repository."
elif [ "$action" == "create" ]; then
mkdir -p "$target_folder"
cd "$target_folder" || { echo "Error: Failed to change directory to $target_folder"; exit 1; }
git init
git config user.email "162507023+nise-moe@users.noreply.github.com"
git config user.name "nise.moe"
git config gpg.format ssh
git config commit.gpgsign true
git config user.signingkey /home/anon/.ssh/nise-moe.pub
git branch -M main
git remote add origin git@github.com:nise-moe/"$folder_to_sync".git
echo "External folder '$target_folder' created and initialized as a Git repository."
else
echo "Error: Invalid action '$action'."
exit 1
fi

View File

@ -43,7 +43,7 @@ class UserService(
} }
fun getUserDetails(identifier: Any): UserDetailsExtended? { fun getUserDetails(identifier: Any): UserDetailsExtended? {
val user = when (identifier) { var user = when (identifier) {
is Long -> dslContext.selectFrom(USERS) is Long -> dslContext.selectFrom(USERS)
.where(USERS.USER_ID.eq(identifier)) .where(USERS.USER_ID.eq(identifier))
.fetchOneInto(UsersRecord::class.java) .fetchOneInto(UsersRecord::class.java)
@ -53,6 +53,16 @@ class UserService(
else -> null else -> null
} }
// Lookup user by ID if we have not found a user via a username lookup and the identifier is a valid number
if (user == null && identifier is String) {
val longIdentifier = identifier.toLongOrNull()
if (longIdentifier != null) {
user = dslContext.selectFrom(USERS)
.where(USERS.USER_ID.eq(longIdentifier))
.fetchOneInto(UsersRecord::class.java)
}
}
if (user != null) { if (user != null) {
val userDetails = UserDetails( val userDetails = UserDetails(
user.userId!!, user.userId!!,
@ -75,7 +85,7 @@ class UserService(
// The database does NOT have the user; we will now use the osu!api // The database does NOT have the user; we will now use the osu!api
val apiUser = when (identifier) { val apiUser = when (identifier) {
is Long -> this.osuApi.getUserProfile(userId = identifier.toString(), mode = "osu", key = "id") is Long -> this.osuApi.getUserProfile(userId = identifier.toString(), mode = "osu", key = "id")
is String -> this.osuApi.getUserProfile(userId = identifier, mode = "osu", key = "username") is String -> this.osuApi.getUserProfile(userId = identifier, mode = "osu")
else -> null else -> null
} ?: return null } ?: return null

View File

@ -0,0 +1,4 @@
CREATE INDEX idx_scores_is_banned ON public.scores(is_banned);
CREATE INDEX idx_scores_pp ON public.scores(pp);
CREATE INDEX idx_scores_sliderend_release_standard_deviation_adjusted ON public.scores(sliderend_release_standard_deviation_adjusted);
CREATE INDEX idx_scores_keypresses_standard_deviation_adjusted ON public.scores(keypresses_standard_deviation_adjusted);

View File

@ -68,7 +68,7 @@ export function getMockReplayData(): ReplayData {
export interface ReplayData { export interface ReplayData {
replay_id: number | null; replay_id: number | null;
user_id: number; user_id: number | null;
username: string; username: string;
date: string; date: string;
beatmap_id: number; beatmap_id: number;

View File

@ -157,7 +157,7 @@
<ng-container *ngIf="column.type == 'string'"> <ng-container *ngIf="column.type == 'string'">
<ng-container *ngIf="column.name == 'user_username'; else stringField"> <ng-container *ngIf="column.name == 'user_username'; else stringField">
<a [href]="'/u/' + getValue(entry, column.name)" target="_blank">{{ getValue(entry, column.name) }}</a> <a [href]="'/u/' + getValue(entry, 'user_id')" target="_blank">{{ getValue(entry, column.name) }}</a>
</ng-container> </ng-container>
<ng-template #stringField> <ng-template #stringField>
{{ getValue(entry, column.name) }} {{ getValue(entry, column.name) }}

View File

@ -346,7 +346,7 @@ export class SearchComponent implements OnInit {
getLink(entry: any): any { getLink(entry: any): any {
if(this.searchType === 'user') { if(this.searchType === 'user') {
return "/u/" + this.getValue(entry, 'username'); return "/u/" + this.getValue(entry, 'user_id');
} else { } else {
return "/s/" + this.getValue(entry, 'replay_id'); return "/s/" + this.getValue(entry, 'replay_id');
} }

View File

@ -0,0 +1,46 @@
import {UserDetails} from './userDetails';
import {SimilarReplay, SuspiciousScore} from './replays';
export class TextReportService {
static generateTextReportForUserScores(
userDetails: UserDetails,
suspiciousScores: SuspiciousScore[],
similarReplays: SimilarReplay[],
) {
const detections: string[] = [];
if (suspiciousScores.length > 0) {
detections.push('Relax');
}
if (similarReplays.length > 0) {
detections.push('Replay Stealing');
}
let report = `[osu!std] ${userDetails.username} | ${detections.join(', ')}\n\n`;
report += `Profile: https://osu.ppy.sh/users/${userDetails.user_id}\n`;
for (const suspiciousScore of suspiciousScores) {
report += `\n${this.getRelaxReport(suspiciousScore)}\n`;
}
for (const similarReplay of similarReplays) {
report += `\n${this.getStealingReport(similarReplay)}\n`;
}
report += `\nGenerated on nise.moe - [${userDetails.username} on nise.moe](https://nise.moe/u/${userDetails.user_id})`;
return report;
}
private static getRelaxReport(suspiciousScore: SuspiciousScore): string {
return `[Replay on ${suspiciousScore.beatmap_title}](https://osu.ppy.sh/scores/osu/${suspiciousScore.replay_id})
cvUR: ${suspiciousScore.ur.toFixed(2)} according to Circleguard`;
}
private static getStealingReport(similarReplay: SimilarReplay): string {
return `[${similarReplay.username_2}'s replay (cheated)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_2})
[${similarReplay.username_1}'s replay (original)](https://osu.ppy.sh/scores/osu/${similarReplay.replay_id_1})
${similarReplay.similarity.toFixed(2)} similarity according to Circleguard`;
}
}

View File

@ -62,11 +62,11 @@
<tr> <tr>
<td>Player</td> <td>Player</td>
<td> <td>
<a [routerLink]="['/u/' + this.pair.replays[0].username]">{{ this.pair.replays[0].username }}</a> <a [routerLink]="['/u/' + this.pair.replays[0].user_id]">{{ this.pair.replays[0].username }}</a>
<a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[0].user_id }}" target="_blank">osu!web</a> <a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[0].user_id }}" target="_blank">osu!web</a>
</td> </td>
<td> <td>
<a [routerLink]="['/u/' + this.pair.replays[1].username]">{{ this.pair.replays[1].username }}</a> <a [routerLink]="['/u/' + this.pair.replays[1].user_id]">{{ this.pair.replays[1].username }}</a>
<a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[1].user_id }}" target="_blank">osu!web</a> <a class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.pair.replays[1].user_id }}" target="_blank">osu!web</a>
</td> </td>
</tr> </tr>

View File

@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="score-player__row score-player__row--player mt-2"> <div class="score-player__row score-player__row--player mt-2">
Played by <a [routerLink]="['/u/' + this.replayData.username]">{{ this.replayData.username }}</a> <a *ngIf="this.replayData.user_id" class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.replayData.user_id }}" target="_blank">osu!web</a> Played by <a [routerLink]="['/u/' + (this.replayData.user_id ?? this.replayData.username)]">{{ this.replayData.username }}</a> <a *ngIf="this.replayData.user_id" class="btn" style="margin-left: 5px" href="https://osu.ppy.sh/users/{{ this.replayData.user_id }}" target="_blank">osu!web</a>
<ng-container *ngIf="!this.isUserScore"> <ng-container *ngIf="!this.isUserScore">
<br> <br>
Submitted on <strong>{{ this.replayData.date }}</strong> Submitted on <strong>{{ this.replayData.date }}</strong>
@ -45,7 +45,7 @@
<div style="display: flex; justify-content: space-between; margin-bottom: 10px" class="link-list"> <div style="display: flex; justify-content: space-between; margin-bottom: 10px" class="link-list">
<a style="flex: 1" class="text-center" href="https://osu.ppy.sh/scores/osu/{{ this.replayData.replay_id }}" target="_blank"> <a style="flex: 1" class="text-center" href="https://osu.ppy.sh/scores/osu/{{ this.replayData.replay_id }}" target="_blank" [class.disabled]="!isWebScore()">
Open in osu!web Open in osu!web
</a> </a>
@ -196,7 +196,7 @@
<tbody> <tbody>
<tr *ngFor="let score of this.replayData.similar_scores"> <tr *ngFor="let score of this.replayData.similar_scores">
<td class="text-center"> <td class="text-center">
<a [routerLink]="['/u/' + score.username]">{{ score.username }}</a> <a [routerLink]="['/u/' + score.user_id]">{{ score.username }}</a>
</td> </td>
<td class="text-center"> <td class="text-center">
{{ score.pp | number: '1.2-2' }} {{ score.pp | number: '1.2-2' }}

View File

@ -63,6 +63,10 @@ export class ViewScoreComponent implements OnInit {
return this.hasErrorDistribution(); return this.hasErrorDistribution();
} }
isWebScore(): boolean {
return !!this.replayData && this.replayData.replay_id !== 0;
}
ngOnInit(): void { ngOnInit(): void {
this.activatedRoute.params.subscribe(params => { this.activatedRoute.params.subscribe(params => {
this.replayId = params['replayId']; this.replayId = params['replayId'];

View File

@ -98,7 +98,7 @@
<tbody style="font-size: 14px;"> <tbody style="font-size: 14px;">
<tr *ngFor="let score of this.getCurrentPage()"> <tr *ngFor="let score of this.getCurrentPage()">
<td> <td>
<a [routerLink]="['/u/' + score.username]"> <a [routerLink]="['/u/' + score.user_id]">
{{ score.username }} {{ score.username }}
</a> </a>
</td> </td>

View File

@ -82,6 +82,12 @@
</ng-template> </ng-template>
</ng-container> </ng-container>
<ng-container *ngIf="this.userInfo.suspicious_scores.length > 0 || this.userInfo.similar_replays.length > 0">
<button (click)="copyReportToClipboard()" class="btn btn-outline-secondary btn-sm">
Generate and copy report
</button>
</ng-container>
<ng-container *ngIf="this.userInfo.suspicious_scores.length > 0"> <ng-container *ngIf="this.userInfo.suspicious_scores.length > 0">
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4> <h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
<div class="table"> <div class="table">

View File

@ -3,7 +3,7 @@ import {SimilarReplay, SuspiciousScore} from "../replays";
import { HttpClient } from "@angular/common/http"; import { HttpClient } from "@angular/common/http";
import {catchError, EMPTY, finalize, Observable, Subscription} from "rxjs"; import {catchError, EMPTY, finalize, Observable, Subscription} from "rxjs";
import {environment} from "../../environments/environment"; import {environment} from "../../environments/environment";
import {DatePipe, DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common"; import {DatePipe, DecimalPipe, JsonPipe, Location, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
import {ActivatedRoute, RouterLink} from "@angular/router"; import {ActivatedRoute, RouterLink} from "@angular/router";
import {UserDetails, UserQueueDetails} from "../userDetails"; import {UserDetails, UserQueueDetails} from "../userDetails";
import {calculateTimeAgo, countryCodeToFlag, formatDuration} from "../format"; import {calculateTimeAgo, countryCodeToFlag, formatDuration} from "../format";
@ -14,6 +14,7 @@ import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-l
import {FilterManagerService} from "../filter-manager.service"; import {FilterManagerService} from "../filter-manager.service";
import {UserService} from "../../corelib/service/user.service"; import {UserService} from "../../corelib/service/user.service";
import {FollowService} from "../../corelib/service/follow.service"; import {FollowService} from "../../corelib/service/follow.service";
import {TextReportService} from '../text-report.service';
interface UserInfo { interface UserInfo {
user_details: UserDetails; user_details: UserDetails;
@ -68,6 +69,7 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private title: Title, private title: Title,
private rxStompService: RxStompService, private rxStompService: RxStompService,
private location: Location,
public userService: UserService, public userService: UserService,
public followService: FollowService public followService: FollowService
) { } ) { }
@ -135,6 +137,8 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
this.subscribeToUser(); this.subscribeToUser();
} }
this.checkIfUserIsFollowed(); this.checkIfUserIsFollowed();
this.location.replaceState(`u/${this.userInfo.user_details.user_id}`);
} }
); );
} }
@ -193,6 +197,20 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
}); });
} }
async copyReportToClipboard(): Promise<void> {
if (!this.userInfo) {
return;
}
const report = TextReportService.generateTextReportForUserScores(
this.userInfo.user_details,
this.userInfo.suspicious_scores,
this.userInfo.similar_replays,
);
await navigator.clipboard.writeText(report);
}
protected readonly formatDuration = formatDuration; protected readonly formatDuration = formatDuration;
protected readonly countryCodeToFlag = countryCodeToFlag; protected readonly countryCodeToFlag = countryCodeToFlag;
protected readonly calculateTimeAgo = calculateTimeAgo; protected readonly calculateTimeAgo = calculateTimeAgo;

2
nise-infra/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.env
*.sql

View File

@ -0,0 +1,165 @@
version: '3'
services:
nginx-main:
image: nginx:latest
container_name: nginx-main
restart: always
volumes:
- ./nginx-main.conf:/etc/nginx/nginx.conf:ro
# nise.moe certificates (by Cloudflare)
- ./nise-data/certificate.pem:/etc/ssl/certs/nisemoe/certificate.pem:ro
- ./nise-data/private.key:/etc/ssl/certs/nisemoe/private.key:ro
ports:
- "443:443"
- "80:80"
# Shared services which are used by others
postgres:
image: groonga/pgroonga:3.1.6-alpine-15
container_name: postgres
restart: always
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
volumes:
- postgres-data:/var/lib/postgresql/data
command: >
-c shared_buffers=6GB
-c effective_cache_size=12GB
-c work_mem=64MB
-c maintenance_work_mem=2GB
-c checkpoint_completion_target=0.9
-c checkpoint_timeout=15min
-c max_wal_size=2GB
-c wal_buffers=16MB
-c max_connections=100
-c max_worker_processes=8
-c max_parallel_workers_per_gather=4
-c max_parallel_workers=8
-c effective_io_concurrency=40
shm_size: '128mb'
redis:
image: redis:alpine
container_name: redis
restart: always
# ------------------------------------------------------------------
gitea:
image: gitea/gitea
container_name: gitea
restart: always
environment:
USER_UID: 1336
USER_GID: 1336
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: ${DB_HOST}:5432
GITEA__database__NAME: gitea
GITEA__database__USER: ${DB_USER}
GITEA__database__PASSWD: ${DB_PASS}
depends_on:
- postgres
- redis
volumes:
- ./gitea-data/app.ini:/data/gitea/conf/app.ini
- gitea-data:/data
# ------------------------------------------------------------------
nise-nginx:
image: nginx:latest
container_name: nise-nginx
restart: always
volumes:
- ./nise-data/nginx.conf:/etc/nginx/nginx.conf:ro
nise-backend:
image: git.nise.moe/nuff/nise-backend:latest
container_name: nise-backend
environment:
SPRING_PROFILES_ACTIVE: postgres,discord,import:scores,import:users,fix:scores
# App configuration
OLD_SCORES_PAGE_SIZE: 1000
# Postgres
POSTGRES_HOST: ${DB_HOST}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASS: ${DB_PASS}
POSTGRES_DB: nise
# redis
REDIS_DB: 4
# Discord
WEBHOOK_URL: ${WEBHOOK_URL}
SCORES_WEBHOOK_URL: ${SCORES_WEBHOOK_URL}
# osu!api
OSU_API_KEY: ${OSU_API_KEY}
OSU_CLIENT_ID: ${OSU_CLIENT_ID}
OSU_CLIENT_SECRET: ${OSU_CLIENT_SECRET}
OSU_CALLBACK: "https://nise.moe/api/login/oauth2/code/osu"
# Metabase
METABASE_API_KEY: ${METABASE_API_KEY}
# Internal API
CIRCLEGUARD_API_URL: http://nise-circleguard:5000
# Auth
ORIGIN: "https://nise.moe"
REPLAY_ORIGIN: "https://replay.nise.moe"
COOKIE_SECURE: false
BEATMAPS_PATH: "/app/dbs"
restart: always
volumes:
- ./nise-data/beatmaps:/app/dbs
depends_on:
- postgres
- redis
nise-circleguard:
image: git.nise.moe/nuff/nise-circleguard:latest
container_name: nise-circleguard
environment:
OSU_API_KEY: ${OSU_API_KEY}
restart: always
volumes:
- ./nise-data/beatmaps:/app/dbs
nise-frontend2:
image: git.nise.moe/nuff/nise-frontend:latest
container_name: nise-frontend2
restart: always
nise-replay-viewer:
image: git.nise.moe/nuff/nise-replay-viewer:latest
container_name: nise-replay-viewer
restart: always
nise-discord:
image: git.nise.moe/nuff/nise-discord:latest
container_name: nise-discord
environment:
DISCORD_TOKEN: ${DISCORD_TOKEN}
REACTION_CHANNEL_ID: ${REACTION_CHANNEL_ID}
REACTION_EMOJI_ID: ${REACTION_EMOJI_ID}
restart: always
nise-metabase:
image: metabase/metabase:latest
container_name: nise-metabase
volumes:
- /dev/urandom:/dev/random:ro
environment:
MB_DB_TYPE: postgres
MB_DB_DBNAME: metabase
MB_DB_PORT: 5432
MB_DB_USER: ${DB_METABASE_USER}
MB_DB_PASS: ${DB_METABASE_PASS}
MB_DB_HOST: postgres
healthcheck:
test: curl --fail -I http://localhost:3000/api/health || exit 1
interval: 15s
timeout: 5s
retries: 5
volumes:
postgres-data:
gitea-data:

View File

@ -0,0 +1,40 @@
events {}
http {
upstream nise-nginx {
server nise-nginx:80;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name nise.moe replay.nise.moe neko.nise.moe;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name nise.moe replay.nise.moe git.nise.moe neko.nise.moe;
ssl_certificate /etc/ssl/certs/nisemoe/certificate.pem;
ssl_certificate_key /etc/ssl/certs/nisemoe/private.key;
location / {
# For git.nise.moe
client_max_body_size 512M;
proxy_pass http://nise-nginx;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}

View File

@ -0,0 +1,94 @@
events {}
http {
upstream gitea {
server gitea:3000;
}
upstream nise-frontend {
server nise-frontend2:80;
}
upstream nise-replay-viewer {
server nise-replay-viewer:80;
}
upstream nise-backend {
server nise-backend:8080;
}
upstream nise-metabase {
server nise-metabase:3000;
}
server {
listen 80;
server_name nise.moe;
location / {
proxy_pass http://nise-frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://nise-backend/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name git.nise.moe;
location / {
client_max_body_size 10G;
proxy_pass http://gitea/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name replay.nise.moe;
location / {
proxy_pass http://nise-replay-viewer/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name neko.nise.moe;
location / {
proxy_pass http://nise-metabase/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

4
nise-infra/sample.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -15,9 +15,8 @@ the website is split into the following modules, each with their own folder and
| nise-backend | The main module, serves an API and processes new scores. | Kotlin, Spring Boot | | nise-backend | The main module, serves an API and processes new scores. | Kotlin, Spring Boot |
| nise-frontend | The frontend module (Angular), uses the API to display data. | Angular | | nise-frontend | The frontend module (Angular), uses the API to display data. | Angular |
| nise-circleguard | Written in Python, serves as an HTTP interface for circleguard. | Python | | nise-circleguard | Written in Python, serves as an HTTP interface for circleguard. | Python |
| konata | Sub-module to detect stolen replays with multithreading support. | Kotlin |
| mari | Sub-module to handle replay files and judgement data | Kotlin |
| nise-discord | Module that runs a Discord bot for role management | Python | | nise-discord | Module that runs a Discord bot for role management | Python |
| nise-infra | Docker (compose) configuration for production deployment | Docker |
| nise-replay-viewer | Standalone react-based website that plays osu!std replays in your browser | React/p5.js | | nise-replay-viewer | Standalone react-based website that plays osu!std replays in your browser | React/p5.js |
# how to run # how to run
@ -28,7 +27,7 @@ you can read the individual readme files for each module to see how to run them
### production ### production
we manage the production stack in a separate `infra` repository. it uses docker-compose to handle the containers, and traefik as a reverse proxy. we manage the production stack in the `nise-infra` folder. it uses docker-compose to handle the containers, and nginx as a reverse proxy.
# contributing # contributing

3
sync-infra.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
rsync -avz --exclude '.git' --exclude '.env' --exclude 'nise-data/beatmaps/*' --exclude 'nise-data/private.key' --exclude 'nise-data/certificate.pem' --exclude 'gitea-data' nise-moe:/root/Lab/ ./nise-infra