Ability for visitors to add users to queue and processing with live progress

This commit is contained in:
nise.moe 2024-02-22 04:39:23 +01:00
parent 45b4334011
commit 63be57dfc8
17 changed files with 338 additions and 53 deletions

View File

@ -17,7 +17,7 @@ import org.jooq.Identity
import org.jooq.Name
import org.jooq.Record
import org.jooq.Records
import org.jooq.Row5
import org.jooq.Row7
import org.jooq.Schema
import org.jooq.SelectField
import org.jooq.Table
@ -88,6 +88,16 @@ open class UpdateUserQueue(
*/
val PROCESSED_AT: TableField<UpdateUserQueueRecord, LocalDateTime?> = createField(DSL.name("processed_at"), SQLDataType.LOCALDATETIME(6), this, "")
/**
* The column <code>public.update_user_queue.progress_current</code>.
*/
val PROGRESS_CURRENT: TableField<UpdateUserQueueRecord, Int?> = createField(DSL.name("progress_current"), SQLDataType.INTEGER, this, "")
/**
* The column <code>public.update_user_queue.progress_total</code>.
*/
val PROGRESS_TOTAL: TableField<UpdateUserQueueRecord, Int?> = createField(DSL.name("progress_total"), SQLDataType.INTEGER, this, "")
private constructor(alias: Name, aliased: Table<UpdateUserQueueRecord>?): this(alias, null, null, aliased, null)
private constructor(alias: Name, aliased: Table<UpdateUserQueueRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
@ -130,18 +140,18 @@ open class UpdateUserQueue(
override fun rename(name: Table<*>): UpdateUserQueue = UpdateUserQueue(name.getQualifiedName(), null)
// -------------------------------------------------------------------------
// Row5 type methods
// Row7 type methods
// -------------------------------------------------------------------------
override fun fieldsRow(): Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> = super.fieldsRow() as Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?>
override fun fieldsRow(): Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> = super.fieldsRow() as Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?>
/**
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
*/
fun <U> mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
fun <U> mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
/**
* Convenience mapping calling {@link SelectField#convertFrom(Class,
* Function)}.
*/
fun <U> mapping(toType: Class<U>, from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
fun <U> mapping(toType: Class<U>, from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
}

View File

@ -10,8 +10,8 @@ import java.time.LocalDateTime
import org.jooq.Field
import org.jooq.Record1
import org.jooq.Record5
import org.jooq.Row5
import org.jooq.Record7
import org.jooq.Row7
import org.jooq.impl.UpdatableRecordImpl
@ -19,7 +19,7 @@ import org.jooq.impl.UpdatableRecordImpl
* This class is generated by jOOQ.
*/
@Suppress("UNCHECKED_CAST")
open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<UpdateUserQueueRecord>(UpdateUserQueue.UPDATE_USER_QUEUE), Record5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> {
open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<UpdateUserQueueRecord>(UpdateUserQueue.UPDATE_USER_QUEUE), Record7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> {
open var id: Int?
set(value): Unit = set(0, value)
@ -41,6 +41,14 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<Upd
set(value): Unit = set(4, value)
get(): LocalDateTime? = get(4) as LocalDateTime?
open var progressCurrent: Int?
set(value): Unit = set(5, value)
get(): Int? = get(5) as Int?
open var progressTotal: Int?
set(value): Unit = set(6, value)
get(): Int? = get(6) as Int?
// -------------------------------------------------------------------------
// Primary key information
// -------------------------------------------------------------------------
@ -48,26 +56,32 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<Upd
override fun key(): Record1<Int?> = super.key() as Record1<Int?>
// -------------------------------------------------------------------------
// Record5 type implementation
// Record7 type implementation
// -------------------------------------------------------------------------
override fun fieldsRow(): Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> = super.fieldsRow() as Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?>
override fun valuesRow(): Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?> = super.valuesRow() as Row5<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?>
override fun fieldsRow(): Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> = super.fieldsRow() as Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?>
override fun valuesRow(): Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?> = super.valuesRow() as Row7<Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?>
override fun field1(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.ID
override fun field2(): Field<Long?> = UpdateUserQueue.UPDATE_USER_QUEUE.USER_ID
override fun field3(): Field<Boolean?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED
override fun field4(): Field<LocalDateTime?> = UpdateUserQueue.UPDATE_USER_QUEUE.CREATED_AT
override fun field5(): Field<LocalDateTime?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED_AT
override fun field6(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_CURRENT
override fun field7(): Field<Int?> = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_TOTAL
override fun component1(): Int? = id
override fun component2(): Long = userId
override fun component3(): Boolean? = processed
override fun component4(): LocalDateTime? = createdAt
override fun component5(): LocalDateTime? = processedAt
override fun component6(): Int? = progressCurrent
override fun component7(): Int? = progressTotal
override fun value1(): Int? = id
override fun value2(): Long = userId
override fun value3(): Boolean? = processed
override fun value4(): LocalDateTime? = createdAt
override fun value5(): LocalDateTime? = processedAt
override fun value6(): Int? = progressCurrent
override fun value7(): Int? = progressTotal
override fun value1(value: Int?): UpdateUserQueueRecord {
set(0, value)
@ -94,24 +108,38 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl<Upd
return this
}
override fun values(value1: Int?, value2: Long?, value3: Boolean?, value4: LocalDateTime?, value5: LocalDateTime?): UpdateUserQueueRecord {
override fun value6(value: Int?): UpdateUserQueueRecord {
set(5, value)
return this
}
override fun value7(value: Int?): UpdateUserQueueRecord {
set(6, value)
return this
}
override fun values(value1: Int?, value2: Long?, value3: Boolean?, value4: LocalDateTime?, value5: LocalDateTime?, value6: Int?, value7: Int?): UpdateUserQueueRecord {
this.value1(value1)
this.value2(value2)
this.value3(value3)
this.value4(value4)
this.value5(value5)
this.value6(value6)
this.value7(value7)
return this
}
/**
* Create a detached, initialised UpdateUserQueueRecord
*/
constructor(id: Int? = null, userId: Long, processed: Boolean? = null, createdAt: LocalDateTime? = null, processedAt: LocalDateTime? = null): this() {
constructor(id: Int? = null, userId: Long, processed: Boolean? = null, createdAt: LocalDateTime? = null, processedAt: LocalDateTime? = null, progressCurrent: Int? = null, progressTotal: Int? = null): this() {
this.id = id
this.userId = userId
this.processed = processed
this.createdAt = createdAt
this.processedAt = processedAt
this.progressCurrent = progressCurrent
this.progressTotal = progressTotal
resetChangedOnNotNull()
}
}

View File

@ -1,6 +1,15 @@
package com.nisemoe.nise
import kotlinx.serialization.Serializable
import java.time.LocalDateTime
data class UserQueueDetails(
val isProcessing: Boolean,
val lastCompletedUpdate: LocalDateTime?,
val progressCurrent: Int?,
val progressTotal: Int?,
)
data class UserDetails(
val user_id: Long,

View File

@ -0,0 +1,31 @@
package com.nisemoe.nise.config
import jakarta.servlet.*
import jakarta.servlet.http.HttpServletResponse
import org.springframework.beans.factory.annotation.Value
class CustomCorsFilter : Filter {
@Value("\${ORIGIN:http://localhost:4200}")
private lateinit var origin: String
override fun init(filterConfig: FilterConfig) {
// We don't really need to do anything special here.
}
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
val response = servletResponse as HttpServletResponse
response.setHeader("Access-Control-Allow-Origin", origin)
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS,PATCH")
response.setHeader(
"Access-Control-Allow-Headers",
"Access-Control-Allow-Headers, Origin,Accept, X-NISE-API, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"
)
response.setHeader("Access-Control-Allow-Credentials", "true")
filterChain.doFilter(servletRequest, servletResponse)
}
override fun destroy() {
// We don't really need to do anything special here.
}
}

View File

@ -14,6 +14,7 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl
import org.springframework.session.web.http.CookieSerializer
import org.springframework.session.web.http.DefaultCookieSerializer
import org.springframework.stereotype.Component
import org.springframework.web.filter.CorsFilter
import java.util.concurrent.TimeUnit
@ -58,10 +59,16 @@ class SecurityConfig {
}
@Bean
fun customCorsFilter(): CustomCorsFilter {
return CustomCorsFilter()
}
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain? {
http
.csrf { csrf -> csrf.disable() }
.addFilterBefore(customCorsFilter(), CorsFilter::class.java)
.oauth2Login { oauthLogin ->
oauthLogin.successHandler(CustomAuthenticationSuccessHandler())
}

View File

@ -1,23 +0,0 @@
package com.nisemoe.nise.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.EnableWebMvc
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration
@EnableWebMvc
class WebConfig: WebMvcConfigurer {
@Value("\${ORIGIN:http://localhost:4200}")
private lateinit var origin: String
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOrigins(origin)
.allowCredentials(true)
}
}

View File

@ -5,24 +5,45 @@ import com.nisemoe.nise.SimilarReplayEntry
import com.nisemoe.nise.database.ScoreService
import com.nisemoe.nise.SuspiciousScoreEntry
import com.nisemoe.nise.UserDetails
import com.nisemoe.nise.UserQueueDetails
import com.nisemoe.nise.database.UserService
import com.nisemoe.nise.service.UpdateUserQueueService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime
@RestController
class UserDetailsController(
private val scoreService: ScoreService,
private val userService: UserService
private val userService: UserService,
private val userQueueService: UpdateUserQueueService
) {
data class UserDetailsResponse(
val user_details: UserDetails,
val queue_details: UserQueueDetails,
val suspicious_scores: List<SuspiciousScoreEntry>,
val similar_replays: List<SimilarReplayEntry>
val similar_replays: List<SimilarReplayEntry>,
val total_scores: Int = 0,
)
data class UserQueueRequest(
val userId: Long
)
@PostMapping("user-queue")
fun addUserToQueue(@RequestBody request: UserQueueRequest): ResponseEntity<Unit> {
val inserted = this.userQueueService.insertUser(request.userId)
return if(inserted)
ResponseEntity.ok().build()
else
ResponseEntity.badRequest().build()
}
@GetMapping("user-details/{userId}")
fun getUserDetails(@PathVariable userId: String): ResponseEntity<UserDetailsResponse> {
val userDetails = this.userService.getUserDetails(username = userId)
@ -33,8 +54,10 @@ class UserDetailsController(
val response = UserDetailsResponse(
user_details = userDetails,
queue_details = this.userQueueService.getUserQueueDetails(userDetails.user_id),
suspicious_scores = this.scoreService.getSuspiciousScores(suspiciousScoresCondition),
similar_replays = this.scoreService.getSimilarReplaysForUserId(userDetails.user_id)
similar_replays = this.scoreService.getSimilarReplaysForUserId(userDetails.user_id),
total_scores = this.userService.getTotalUserScores(userDetails.user_id)
)
return ResponseEntity.ok(response)
}

View File

@ -1,11 +1,15 @@
package com.nisemoe.nise.database
import com.nisemoe.generated.tables.records.UpdateUserQueueRecord
import com.nisemoe.generated.tables.records.UsersRecord
import com.nisemoe.generated.tables.references.SCORES
import com.nisemoe.generated.tables.references.UPDATE_USER_QUEUE
import com.nisemoe.generated.tables.references.USERS
import com.nisemoe.nise.osu.OsuApi
import com.nisemoe.nise.osu.OsuApiModels
import com.nisemoe.nise.Format
import com.nisemoe.nise.UserDetails
import com.nisemoe.nise.UserQueueDetails
import com.nisemoe.nise.osu.OsuApi
import com.nisemoe.nise.osu.OsuApiModels
import org.jooq.DSLContext
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
@ -36,6 +40,10 @@ class UserService(
}
fun getTotalUserScores(userId: Long): Int {
return dslContext.fetchCount(SCORES, SCORES.USER_ID.eq(userId))
}
fun getUserDetails(username: String): UserDetails? {
val user = dslContext.selectFrom(USERS)
.where(USERS.USERNAME.equalIgnoreCase(username.lowercase()))

View File

@ -6,6 +6,7 @@ import com.nisemoe.konata.Replay
import com.nisemoe.konata.ReplaySetComparison
import com.nisemoe.konata.compareReplaySet
import com.nisemoe.nise.Format.Companion.fromJudgementType
import com.nisemoe.nise.UserQueueDetails
import com.nisemoe.nise.database.ScoreService
import com.nisemoe.nise.database.UserService
import com.nisemoe.nise.integrations.CircleguardService
@ -169,6 +170,16 @@ class ImportScores(
}
}
var current = 0
val lastCompletedUpdate = dslContext.select(UPDATE_USER_QUEUE.PROCESSED_AT)
.from(UPDATE_USER_QUEUE)
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
.and(UPDATE_USER_QUEUE.PROCESSED.isTrue)
.orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc())
.limit(1)
.fetchOneInto(LocalDateTime::class.java)
for(topScore in topUserScores) {
if(topScore.beatmap != null && topScore.beatmapset != null) {
val beatmapExists = dslContext.fetchExists(BEATMAPS, BEATMAPS.BEATMAP_ID.eq(topScore.beatmap.id))
@ -187,10 +198,34 @@ class ImportScores(
this.statistics.beatmapsAddedToDatabase++
}
// Update the database
dslContext.update(UPDATE_USER_QUEUE)
.set(UPDATE_USER_QUEUE.PROGRESS_CURRENT, current)
.set(UPDATE_USER_QUEUE.PROGRESS_TOTAL, topUserScores.size)
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
.execute()
val currentQueueDetails = UserQueueDetails(
isProcessing = true,
lastCompletedUpdate = lastCompletedUpdate,
progressCurrent = current,
progressTotal = topUserScores.size
)
// Update the frontend
messagingTemplate.convertAndSend(
"/topic/live-user/${userId}",
currentQueueDetails
)
this.insertAndProcessNewScore(topScore.beatmap.id, topScore)
}
current += 1
}
}
// Update the backend
this.updateUserQueueService.setUserAsProcessed(userId)
}

View File

@ -1,15 +1,53 @@
package com.nisemoe.nise.service
import com.nisemoe.generated.tables.records.UpdateUserQueueRecord
import com.nisemoe.generated.tables.references.UPDATE_USER_QUEUE
import com.nisemoe.nise.UserQueueDetails
import org.jooq.DSLContext
import org.springframework.messaging.simp.SimpMessagingTemplate
import org.springframework.stereotype.Service
import java.time.LocalDateTime
@Service
class UpdateUserQueueService(
private val dslContext: DSLContext
private val dslContext: DSLContext,
private val messagingTemplate: SimpMessagingTemplate
) {
fun getUserQueueDetails(userId: Long): UserQueueDetails {
val isProcessing = dslContext.fetchExists(
UPDATE_USER_QUEUE,
UPDATE_USER_QUEUE.USER_ID.eq(userId).and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
)
val lastCompletedUpdate = dslContext.select(UPDATE_USER_QUEUE.PROCESSED_AT)
.from(UPDATE_USER_QUEUE)
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
.and(UPDATE_USER_QUEUE.PROCESSED.isTrue)
.orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc())
.limit(1)
.fetchOneInto(LocalDateTime::class.java)
val currentProgress = dslContext.select(
UPDATE_USER_QUEUE.PROGRESS_CURRENT,
UPDATE_USER_QUEUE.PROGRESS_TOTAL
)
.from(UPDATE_USER_QUEUE)
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
.orderBy(UPDATE_USER_QUEUE.PROCESSED_AT.desc())
.limit(1)
.fetchOneInto(UpdateUserQueueRecord::class.java)
return UserQueueDetails(
isProcessing,
lastCompletedUpdate,
currentProgress?.progressCurrent,
currentProgress?.progressTotal
)
}
fun getQueue(): List<Long> {
return dslContext.select(UPDATE_USER_QUEUE.USER_ID)
.from(UPDATE_USER_QUEUE)
@ -18,17 +56,23 @@ class UpdateUserQueueService(
.fetchInto(Long::class.java)
}
fun insertUser(userId: Long) {
/**
* Inserts a user into the update queue if they are not already in the queue.
* @return true if the user was inserted, false if the user was already in the queue
*/
fun insertUser(userId: Long): Boolean {
val exists = dslContext.fetchExists(UPDATE_USER_QUEUE,
UPDATE_USER_QUEUE.USER_ID.eq(userId),
UPDATE_USER_QUEUE.PROCESSED.isFalse
)
if (exists)
return
return false
dslContext.insertInto(UPDATE_USER_QUEUE)
val insertedRows = dslContext.insertInto(UPDATE_USER_QUEUE)
.set(UPDATE_USER_QUEUE.USER_ID, userId)
.execute()
return insertedRows == 1
}
fun setUserAsProcessed(userId: Long) {
@ -38,7 +82,12 @@ class UpdateUserQueueService(
.where(UPDATE_USER_QUEUE.USER_ID.eq(userId))
.and(UPDATE_USER_QUEUE.PROCESSED.isFalse)
.execute()
// Notify the user that their queue has been processed with fresh info
messagingTemplate.convertAndSend(
"/topic/live-user/${userId}",
this.getUserQueueDetails(userId)
)
}
}

View File

@ -0,0 +1,5 @@
ALTER TABLE public.update_user_queue
ADD COLUMN progress_current int;
ALTER TABLE public.update_user_queue
ADD COLUMN progress_total int;

View File

@ -11,3 +11,11 @@ export interface UserDetails {
suspicious_scores?: number;
}
export interface UserQueueDetails {
isProcessing: boolean;
lastCompletedUpdate: string | null;
progressCurrent: number | null;
progressTotal: number | null;
}

View File

@ -40,6 +40,24 @@
<li *ngIf="this.userInfo.user_details.playcount">Playcount: {{ this.userInfo.user_details.playcount | number: '1.0-1' }}</li>
</ul>
<ng-container *ngIf="!this.userInfo.queue_details.isProcessing; else updateProgress">
<a (click)="this.addUserToQueue()" class="btn btn-success pointer" target="_blank">
update user scores now!
</a> <span style="margin-left: 4px">|</span>
last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.userInfo.queue_details.lastCompletedUpdate : 'never'}}
</ng-container>
<ng-template #updateProgress>
<div class="progress">
<span class="btn-warning">updating now!</span> <span style="margin-left: 4px">|</span> progress: {{ this.userInfo.queue_details.progressCurrent ? this.userInfo.queue_details.progressCurrent : "?" }}/{{ this.userInfo.queue_details.progressTotal ? this.userInfo.queue_details.progressTotal : "?" }}
<ng-container *ngIf="!this.userInfo.queue_details.progressTotal && !this.userInfo.queue_details.progressCurrent; else loading">
<span style="font-weight: bold">(in queue)</span>
</ng-container>
<ng-template #loading>
<app-cute-loading></app-cute-loading>
</ng-template>
</div>
</ng-template>
<h4>Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})</h4>
<div class="table">
<table class="table">

View File

@ -1,18 +1,23 @@
import {Component, OnChanges, OnInit} from '@angular/core';
import {Component, OnChanges, OnDestroy, OnInit} from '@angular/core';
import {SimilarReplay, SuspiciousScore} from "../replays";
import {HttpClient} from "@angular/common/http";
import {catchError, EMPTY, finalize, Observable} from "rxjs";
import {catchError, EMPTY, finalize, Observable, Subscription} from "rxjs";
import {environment} from "../../environments/environment";
import {DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
import {ActivatedRoute, RouterLink} from "@angular/router";
import {UserDetails} from "../userDetails";
import {UserDetails, UserQueueDetails} from "../userDetails";
import {countryCodeToFlag, formatDuration} from "../format";
import {Title} from "@angular/platform-browser";
import {RxStompService} from "../../corelib/stomp/stomp.service";
import {Message} from "@stomp/stompjs/esm6";
import {CuteLoadingComponent} from "../../corelib/components/cute-loading/cute-loading.component";
interface UserInfo {
user_details: UserDetails;
queue_details: UserQueueDetails;
suspicious_scores: SuspiciousScore[];
similar_replays: SimilarReplay[];
total_scores: number;
}
@Component({
@ -24,22 +29,26 @@ interface UserInfo {
NgForOf,
RouterLink,
NgIf,
NgOptimizedImage
NgOptimizedImage,
CuteLoadingComponent
],
templateUrl: './view-user.component.html',
styleUrl: './view-user.component.css'
})
export class ViewUserComponent implements OnInit, OnChanges {
export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
isLoading = false;
notFound = false;
userId: string | null = null;
userInfo: UserInfo | null = null;
liveUserSub: Subscription | undefined;
constructor(
private httpClient: HttpClient,
private activatedRoute: ActivatedRoute,
private title: Title
private title: Title,
private rxStompService: RxStompService,
) { }
getUserInfo(): Observable<UserInfo> {
@ -74,14 +83,40 @@ export class ViewUserComponent implements OnInit, OnChanges {
this.notFound = false;
this.userInfo = response;
this.title.setTitle(`${this.userInfo.user_details.username}`);
this.subscribeToUser();
}
);
}
});
}
protected readonly formatDuration = formatDuration;
ngOnDestroy(): void {
this.liveUserSub?.unsubscribe();
}
addUserToQueue(): void {
const body = {
userId: this.userInfo?.user_details.user_id
}
this.httpClient.post(`${environment.apiUrl}/user-queue`, body).subscribe(
() => {
this.userInfo!.queue_details.isProcessing = true;
},
(error) => {
alert("Failed to add user to queue.");
}
)
}
subscribeToUser(): void {
this.liveUserSub = this.rxStompService
.watch(`/topic/live-user/${this.userInfo?.user_details.user_id}`)
.subscribe((message: Message) => {
this.userInfo!.queue_details = JSON.parse(message.body);
});
}
protected readonly formatDuration = formatDuration;
protected readonly countryCodeToFlag = countryCodeToFlag;

View File

@ -141,6 +141,27 @@ a.btn {
border: 1px dotted #b3b8c3;
}
a.btn-success {
color: #5eaf78;
border: 1px dotted #5eaf78;
}
a.btn-success:hover {
color: #2af171;
border: 1px dotted #2af171;
}
.btn-warning {
padding: 2px;
color: #f1c40f;
border: 1px dotted #f1c40f;
}
.btn-warning:hover {
color: #f1c40f;
border: 1px dotted #f1c40f;
}
.text-center {
text-align: center;
}

View File

@ -0,0 +1,20 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-cute-loading',
standalone: true,
templateUrl: './cute-loading.component.html'
})
export class CuteLoadingComponent implements OnInit {
dots = '';
ngOnInit() {
setInterval(() => {
this.dots += '.';
if (this.dots.length > 3) {
this.dots = '.';
}
}, 500);
}
}