From 63be57dfc8112ffe92792d1d14b878ae7b8e8627 Mon Sep 17 00:00:00 2001 From: "nise.moe" Date: Thu, 22 Feb 2024 04:39:23 +0100 Subject: [PATCH] Ability for visitors to add users to queue and processing with live progress --- .../generated/tables/UpdateUserQueue.kt | 20 +++++-- .../tables/records/UpdateUserQueueRecord.kt | 44 +++++++++++--- .../main/kotlin/com/nisemoe/nise/Models.kt | 9 +++ .../nisemoe/nise/config/CustomCorsFilter.kt | 31 ++++++++++ .../com/nisemoe/nise/config/SecurityConfig.kt | 7 +++ .../com/nisemoe/nise/config/WebConfig.kt | 23 -------- .../nise/controller/UserDetailsController.kt | 29 ++++++++- .../com/nisemoe/nise/database/UserService.kt | 12 +++- .../nisemoe/nise/scheduler/ImportScores.kt | 35 +++++++++++ .../nise/service/UpdateUserQueueService.kt | 59 +++++++++++++++++-- .../V0.0.1.017__alter_users_queue.sql | 5 ++ nise-frontend/src/app/userDetails.ts | 8 +++ .../app/view-user/view-user.component.html | 18 ++++++ .../src/app/view-user/view-user.component.ts | 49 ++++++++++++--- nise-frontend/src/assets/style.css | 21 +++++++ .../cute-loading/cute-loading.component.html | 1 + .../cute-loading/cute-loading.component.ts | 20 +++++++ 17 files changed, 338 insertions(+), 53 deletions(-) create mode 100644 nise-backend/src/main/kotlin/com/nisemoe/nise/config/CustomCorsFilter.kt delete mode 100644 nise-backend/src/main/kotlin/com/nisemoe/nise/config/WebConfig.kt create mode 100644 nise-backend/src/main/resources/db/migration/V0.0.1.017__alter_users_queue.sql create mode 100644 nise-frontend/src/corelib/components/cute-loading/cute-loading.component.html create mode 100644 nise-frontend/src/corelib/components/cute-loading/cute-loading.component.ts diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt index eced4ed..56d0226 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/UpdateUserQueue.kt @@ -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 = createField(DSL.name("processed_at"), SQLDataType.LOCALDATETIME(6), this, "") + /** + * The column public.update_user_queue.progress_current. + */ + val PROGRESS_CURRENT: TableField = createField(DSL.name("progress_current"), SQLDataType.INTEGER, this, "") + + /** + * The column public.update_user_queue.progress_total. + */ + val PROGRESS_TOTAL: TableField = createField(DSL.name("progress_total"), SQLDataType.INTEGER, this, "") + private constructor(alias: Name, aliased: Table?): this(alias, null, null, aliased, null) private constructor(alias: Name, aliased: Table?, parameters: Array?>?): 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 = super.fieldsRow() as Row5 + override fun fieldsRow(): Row7 = super.fieldsRow() as Row7 /** * Convenience mapping calling {@link SelectField#convertFrom(Function)}. */ - fun mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?) -> U): SelectField = convertFrom(Records.mapping(from)) + fun mapping(from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> U): SelectField = convertFrom(Records.mapping(from)) /** * Convenience mapping calling {@link SelectField#convertFrom(Class, * Function)}. */ - fun mapping(toType: Class, from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) + fun mapping(toType: Class, from: (Int?, Long?, Boolean?, LocalDateTime?, LocalDateTime?, Int?, Int?) -> U): SelectField = convertFrom(toType, Records.mapping(from)) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/UpdateUserQueueRecord.kt b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/UpdateUserQueueRecord.kt index 8ae7eeb..3024e36 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/UpdateUserQueueRecord.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/generated/tables/records/UpdateUserQueueRecord.kt @@ -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(UpdateUserQueue.UPDATE_USER_QUEUE), Record5 { +open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl(UpdateUserQueue.UPDATE_USER_QUEUE), Record7 { open var id: Int? set(value): Unit = set(0, value) @@ -41,6 +41,14 @@ open class UpdateUserQueueRecord private constructor() : UpdatableRecordImpl = super.key() as Record1 // ------------------------------------------------------------------------- - // Record5 type implementation + // Record7 type implementation // ------------------------------------------------------------------------- - override fun fieldsRow(): Row5 = super.fieldsRow() as Row5 - override fun valuesRow(): Row5 = super.valuesRow() as Row5 + override fun fieldsRow(): Row7 = super.fieldsRow() as Row7 + override fun valuesRow(): Row7 = super.valuesRow() as Row7 override fun field1(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.ID override fun field2(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.USER_ID override fun field3(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED override fun field4(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.CREATED_AT override fun field5(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROCESSED_AT + override fun field6(): Field = UpdateUserQueue.UPDATE_USER_QUEUE.PROGRESS_CURRENT + override fun field7(): Field = 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 csrf.disable() } + .addFilterBefore(customCorsFilter(), CorsFilter::class.java) .oauth2Login { oauthLogin -> oauthLogin.successHandler(CustomAuthenticationSuccessHandler()) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/config/WebConfig.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/config/WebConfig.kt deleted file mode 100644 index 8ddef8b..0000000 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/config/WebConfig.kt +++ /dev/null @@ -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) - } - -} \ No newline at end of file diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt index fc54df5..eace9bc 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/controller/UserDetailsController.kt @@ -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, - val similar_replays: List + val similar_replays: List, + val total_scores: Int = 0, ) + data class UserQueueRequest( + val userId: Long + ) + + @PostMapping("user-queue") + fun addUserToQueue(@RequestBody request: UserQueueRequest): ResponseEntity { + 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 { 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) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt index 6108c28..3e9d6ba 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserService.kt @@ -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())) diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt index 2f79bf5..ff42064 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/scheduler/ImportScores.kt @@ -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) } diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt index 7387966..3709af5 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/service/UpdateUserQueueService.kt @@ -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 { 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) + ) } - } \ No newline at end of file diff --git a/nise-backend/src/main/resources/db/migration/V0.0.1.017__alter_users_queue.sql b/nise-backend/src/main/resources/db/migration/V0.0.1.017__alter_users_queue.sql new file mode 100644 index 0000000..0d2c4a3 --- /dev/null +++ b/nise-backend/src/main/resources/db/migration/V0.0.1.017__alter_users_queue.sql @@ -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; \ No newline at end of file diff --git a/nise-frontend/src/app/userDetails.ts b/nise-frontend/src/app/userDetails.ts index 53aa849..07e1288 100644 --- a/nise-frontend/src/app/userDetails.ts +++ b/nise-frontend/src/app/userDetails.ts @@ -11,3 +11,11 @@ export interface UserDetails { suspicious_scores?: number; } + +export interface UserQueueDetails { + isProcessing: boolean; + lastCompletedUpdate: string | null; + + progressCurrent: number | null; + progressTotal: number | null; +} diff --git a/nise-frontend/src/app/view-user/view-user.component.html b/nise-frontend/src/app/view-user/view-user.component.html index cb25251..6d1903a 100644 --- a/nise-frontend/src/app/view-user/view-user.component.html +++ b/nise-frontend/src/app/view-user/view-user.component.html @@ -40,6 +40,24 @@
  • Playcount: {{ this.userInfo.user_details.playcount | number: '1.0-1' }}
  • + + + update user scores now! + | + last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.userInfo.queue_details.lastCompletedUpdate : 'never'}} + + +
    + updating now! | progress: {{ this.userInfo.queue_details.progressCurrent ? this.userInfo.queue_details.progressCurrent : "?" }}/{{ this.userInfo.queue_details.progressTotal ? this.userInfo.queue_details.progressTotal : "?" }} + + (in queue) + + + + +
    +
    +

    Suspicious Scores ({{ this.userInfo.suspicious_scores.length }})

    diff --git a/nise-frontend/src/app/view-user/view-user.component.ts b/nise-frontend/src/app/view-user/view-user.component.ts index dd05527..c02e323 100644 --- a/nise-frontend/src/app/view-user/view-user.component.ts +++ b/nise-frontend/src/app/view-user/view-user.component.ts @@ -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 { @@ -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; diff --git a/nise-frontend/src/assets/style.css b/nise-frontend/src/assets/style.css index 206f1a6..5943fe0 100644 --- a/nise-frontend/src/assets/style.css +++ b/nise-frontend/src/assets/style.css @@ -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; } diff --git a/nise-frontend/src/corelib/components/cute-loading/cute-loading.component.html b/nise-frontend/src/corelib/components/cute-loading/cute-loading.component.html new file mode 100644 index 0000000..5f5b05e --- /dev/null +++ b/nise-frontend/src/corelib/components/cute-loading/cute-loading.component.html @@ -0,0 +1 @@ +{{ dots }} diff --git a/nise-frontend/src/corelib/components/cute-loading/cute-loading.component.ts b/nise-frontend/src/corelib/components/cute-loading/cute-loading.component.ts new file mode 100644 index 0000000..2bfee75 --- /dev/null +++ b/nise-frontend/src/corelib/components/cute-loading/cute-loading.component.ts @@ -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); + } + +}