Added basic follows
This commit is contained in:
parent
e14367edaf
commit
0ca65307b5
1
mari/.github/FUNDING.yml
vendored
Normal file
1
mari/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
patreon: nise_moe
|
||||
@ -1,7 +1,32 @@
|
||||
package org.nisemoe.mari.judgements
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Backwards compatibility with time values that were persisted as doubles.
|
||||
*/
|
||||
object TimeSerializer : KSerializer<Int> {
|
||||
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Time", PrimitiveKind.INT)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Int) {
|
||||
encoder.encodeInt(value)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Int {
|
||||
val doubleValue = decoder.decodeDouble()
|
||||
return doubleValue.roundToInt()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a judgement on a hit object.
|
||||
@ -9,7 +34,9 @@ import kotlinx.serialization.Serializable
|
||||
*/
|
||||
@Serializable
|
||||
data class Judgement(
|
||||
@Serializable(with = TimeSerializer::class)
|
||||
val time: Int,
|
||||
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
val type: Type,
|
||||
|
||||
@ -22,6 +22,7 @@ import com.nisemoe.generated.tables.Scores
|
||||
import com.nisemoe.generated.tables.ScoresJudgements
|
||||
import com.nisemoe.generated.tables.ScoresSimilarity
|
||||
import com.nisemoe.generated.tables.UpdateUserQueue
|
||||
import com.nisemoe.generated.tables.UserFollows
|
||||
import com.nisemoe.generated.tables.UserScores
|
||||
import com.nisemoe.generated.tables.UserScoresSimilarity
|
||||
import com.nisemoe.generated.tables.Users
|
||||
@ -87,6 +88,11 @@ open class Public : SchemaImpl("public", DefaultCatalog.DEFAULT_CATALOG) {
|
||||
*/
|
||||
val UPDATE_USER_QUEUE: UpdateUserQueue get() = UpdateUserQueue.UPDATE_USER_QUEUE
|
||||
|
||||
/**
|
||||
* The table <code>public.user_follows</code>.
|
||||
*/
|
||||
val USER_FOLLOWS: UserFollows get() = UserFollows.USER_FOLLOWS
|
||||
|
||||
/**
|
||||
* The table <code>public.user_scores</code>.
|
||||
*/
|
||||
@ -126,6 +132,7 @@ open class Public : SchemaImpl("public", DefaultCatalog.DEFAULT_CATALOG) {
|
||||
ScoresJudgements.SCORES_JUDGEMENTS,
|
||||
ScoresSimilarity.SCORES_SIMILARITY,
|
||||
UpdateUserQueue.UPDATE_USER_QUEUE,
|
||||
UserFollows.USER_FOLLOWS,
|
||||
UserScores.USER_SCORES,
|
||||
UserScoresSimilarity.USER_SCORES_SIMILARITY,
|
||||
Users.USERS
|
||||
|
||||
@ -12,6 +12,7 @@ import com.nisemoe.generated.tables.Scores
|
||||
import com.nisemoe.generated.tables.ScoresJudgements
|
||||
import com.nisemoe.generated.tables.ScoresSimilarity
|
||||
import com.nisemoe.generated.tables.UpdateUserQueue
|
||||
import com.nisemoe.generated.tables.UserFollows
|
||||
import com.nisemoe.generated.tables.UserScoresSimilarity
|
||||
import com.nisemoe.generated.tables.Users
|
||||
import com.nisemoe.generated.tables.records.BeatmapsRecord
|
||||
@ -22,6 +23,7 @@ import com.nisemoe.generated.tables.records.ScoresJudgementsRecord
|
||||
import com.nisemoe.generated.tables.records.ScoresRecord
|
||||
import com.nisemoe.generated.tables.records.ScoresSimilarityRecord
|
||||
import com.nisemoe.generated.tables.records.UpdateUserQueueRecord
|
||||
import com.nisemoe.generated.tables.records.UserFollowsRecord
|
||||
import com.nisemoe.generated.tables.records.UserScoresSimilarityRecord
|
||||
import com.nisemoe.generated.tables.records.UsersRecord
|
||||
|
||||
@ -46,6 +48,7 @@ val SCORES_JUDGEMENTS_PKEY: UniqueKey<ScoresJudgementsRecord> = Internal.createU
|
||||
val SCORES_SIMILARITY_PKEY: UniqueKey<ScoresSimilarityRecord> = Internal.createUniqueKey(ScoresSimilarity.SCORES_SIMILARITY, DSL.name("scores_similarity_pkey"), arrayOf(ScoresSimilarity.SCORES_SIMILARITY.ID), true)
|
||||
val UNIQUE_BEATMAP_REPLAY_IDS: UniqueKey<ScoresSimilarityRecord> = Internal.createUniqueKey(ScoresSimilarity.SCORES_SIMILARITY, DSL.name("unique_beatmap_replay_ids"), arrayOf(ScoresSimilarity.SCORES_SIMILARITY.BEATMAP_ID, ScoresSimilarity.SCORES_SIMILARITY.REPLAY_ID_1, ScoresSimilarity.SCORES_SIMILARITY.REPLAY_ID_2), true)
|
||||
val UPDATE_USER_QUEUE_PKEY: UniqueKey<UpdateUserQueueRecord> = Internal.createUniqueKey(UpdateUserQueue.UPDATE_USER_QUEUE, DSL.name("update_user_queue_pkey"), arrayOf(UpdateUserQueue.UPDATE_USER_QUEUE.ID), true)
|
||||
val USER_FOLLOWS_PKEY: UniqueKey<UserFollowsRecord> = Internal.createUniqueKey(UserFollows.USER_FOLLOWS, DSL.name("user_follows_pkey"), arrayOf(UserFollows.USER_FOLLOWS.USER_ID, UserFollows.USER_FOLLOWS.FOLLOWS_USER_ID), true)
|
||||
val USER_SCORES_SIMILARITY_PKEY: UniqueKey<UserScoresSimilarityRecord> = Internal.createUniqueKey(UserScoresSimilarity.USER_SCORES_SIMILARITY, DSL.name("user_scores_similarity_pkey"), arrayOf(UserScoresSimilarity.USER_SCORES_SIMILARITY.ID), true)
|
||||
val USER_SCORES_UNIQUE_BEATMAP_REPLAY_IDS: UniqueKey<UserScoresSimilarityRecord> = Internal.createUniqueKey(UserScoresSimilarity.USER_SCORES_SIMILARITY, DSL.name("user_scores_unique_beatmap_replay_ids"), arrayOf(UserScoresSimilarity.USER_SCORES_SIMILARITY.BEATMAP_ID, UserScoresSimilarity.USER_SCORES_SIMILARITY.REPLAY_ID_USER, UserScoresSimilarity.USER_SCORES_SIMILARITY.REPLAY_ID_OSU), true)
|
||||
val USERS_PKEY: UniqueKey<UsersRecord> = Internal.createUniqueKey(Users.USERS, DSL.name("users_pkey"), arrayOf(Users.USERS.USER_ID), true)
|
||||
@ -55,3 +58,5 @@ val USERS_PKEY: UniqueKey<UsersRecord> = Internal.createUniqueKey(Users.USERS, D
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
val SCORES_JUDGEMENTS__SCORES_JUDGEMENTS_SCORE_ID_FKEY: ForeignKey<ScoresJudgementsRecord, ScoresRecord> = Internal.createForeignKey(ScoresJudgements.SCORES_JUDGEMENTS, DSL.name("scores_judgements_score_id_fkey"), arrayOf(ScoresJudgements.SCORES_JUDGEMENTS.SCORE_ID), com.nisemoe.generated.keys.SCORES_PKEY, arrayOf(Scores.SCORES.ID), true)
|
||||
val USER_FOLLOWS__USER_FOLLOWS_FOLLOWS_USER_ID_FKEY: ForeignKey<UserFollowsRecord, UsersRecord> = Internal.createForeignKey(UserFollows.USER_FOLLOWS, DSL.name("user_follows_follows_user_id_fkey"), arrayOf(UserFollows.USER_FOLLOWS.FOLLOWS_USER_ID), com.nisemoe.generated.keys.USERS_PKEY, arrayOf(Users.USERS.USER_ID), true)
|
||||
val USER_FOLLOWS__USER_FOLLOWS_USER_ID_FKEY: ForeignKey<UserFollowsRecord, UsersRecord> = Internal.createForeignKey(UserFollows.USER_FOLLOWS, DSL.name("user_follows_user_id_fkey"), arrayOf(UserFollows.USER_FOLLOWS.USER_ID), com.nisemoe.generated.keys.USERS_PKEY, arrayOf(Users.USERS.USER_ID), true)
|
||||
|
||||
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* This file is generated by jOOQ.
|
||||
*/
|
||||
package com.nisemoe.generated.tables
|
||||
|
||||
|
||||
import com.nisemoe.generated.Public
|
||||
import com.nisemoe.generated.keys.USER_FOLLOWS_PKEY
|
||||
import com.nisemoe.generated.keys.USER_FOLLOWS__USER_FOLLOWS_FOLLOWS_USER_ID_FKEY
|
||||
import com.nisemoe.generated.keys.USER_FOLLOWS__USER_FOLLOWS_USER_ID_FKEY
|
||||
import com.nisemoe.generated.tables.records.UserFollowsRecord
|
||||
|
||||
import java.util.function.Function
|
||||
|
||||
import kotlin.collections.List
|
||||
|
||||
import org.jooq.Field
|
||||
import org.jooq.ForeignKey
|
||||
import org.jooq.Identity
|
||||
import org.jooq.Name
|
||||
import org.jooq.Record
|
||||
import org.jooq.Records
|
||||
import org.jooq.Row2
|
||||
import org.jooq.Schema
|
||||
import org.jooq.SelectField
|
||||
import org.jooq.Table
|
||||
import org.jooq.TableField
|
||||
import org.jooq.TableOptions
|
||||
import org.jooq.UniqueKey
|
||||
import org.jooq.impl.DSL
|
||||
import org.jooq.impl.Internal
|
||||
import org.jooq.impl.SQLDataType
|
||||
import org.jooq.impl.TableImpl
|
||||
|
||||
|
||||
/**
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
open class UserFollows(
|
||||
alias: Name,
|
||||
child: Table<out Record>?,
|
||||
path: ForeignKey<out Record, UserFollowsRecord>?,
|
||||
aliased: Table<UserFollowsRecord>?,
|
||||
parameters: Array<Field<*>?>?
|
||||
): TableImpl<UserFollowsRecord>(
|
||||
alias,
|
||||
Public.PUBLIC,
|
||||
child,
|
||||
path,
|
||||
aliased,
|
||||
parameters,
|
||||
DSL.comment(""),
|
||||
TableOptions.table()
|
||||
) {
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* The reference instance of <code>public.user_follows</code>
|
||||
*/
|
||||
val USER_FOLLOWS: UserFollows = UserFollows()
|
||||
}
|
||||
|
||||
/**
|
||||
* The class holding records for this type
|
||||
*/
|
||||
override fun getRecordType(): Class<UserFollowsRecord> = UserFollowsRecord::class.java
|
||||
|
||||
/**
|
||||
* The column <code>public.user_follows.user_id</code>.
|
||||
*/
|
||||
val USER_ID: TableField<UserFollowsRecord, Long?> = createField(DSL.name("user_id"), SQLDataType.BIGINT.nullable(false).identity(true), this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.user_follows.follows_user_id</code>.
|
||||
*/
|
||||
val FOLLOWS_USER_ID: TableField<UserFollowsRecord, Long?> = createField(DSL.name("follows_user_id"), SQLDataType.BIGINT.nullable(false).identity(true), this, "")
|
||||
|
||||
private constructor(alias: Name, aliased: Table<UserFollowsRecord>?): this(alias, null, null, aliased, null)
|
||||
private constructor(alias: Name, aliased: Table<UserFollowsRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
||||
|
||||
/**
|
||||
* Create an aliased <code>public.user_follows</code> table reference
|
||||
*/
|
||||
constructor(alias: String): this(DSL.name(alias))
|
||||
|
||||
/**
|
||||
* Create an aliased <code>public.user_follows</code> table reference
|
||||
*/
|
||||
constructor(alias: Name): this(alias, null)
|
||||
|
||||
/**
|
||||
* Create a <code>public.user_follows</code> table reference
|
||||
*/
|
||||
constructor(): this(DSL.name("user_follows"), null)
|
||||
|
||||
constructor(child: Table<out Record>, key: ForeignKey<out Record, UserFollowsRecord>): this(Internal.createPathAlias(child, key), child, key, USER_FOLLOWS, null)
|
||||
override fun getSchema(): Schema? = if (aliased()) null else Public.PUBLIC
|
||||
override fun getIdentity(): Identity<UserFollowsRecord, Long?> = super.getIdentity() as Identity<UserFollowsRecord, Long?>
|
||||
override fun getPrimaryKey(): UniqueKey<UserFollowsRecord> = USER_FOLLOWS_PKEY
|
||||
override fun getReferences(): List<ForeignKey<UserFollowsRecord, *>> = listOf(USER_FOLLOWS__USER_FOLLOWS_USER_ID_FKEY, USER_FOLLOWS__USER_FOLLOWS_FOLLOWS_USER_ID_FKEY)
|
||||
|
||||
private lateinit var _userFollowsUserIdFkey: Users
|
||||
private lateinit var _userFollowsFollowsUserIdFkey: Users
|
||||
|
||||
/**
|
||||
* Get the implicit join path to the <code>public.users</code> table, via
|
||||
* the <code>user_follows_user_id_fkey</code> key.
|
||||
*/
|
||||
fun userFollowsUserIdFkey(): Users {
|
||||
if (!this::_userFollowsUserIdFkey.isInitialized)
|
||||
_userFollowsUserIdFkey = Users(this, USER_FOLLOWS__USER_FOLLOWS_USER_ID_FKEY)
|
||||
|
||||
return _userFollowsUserIdFkey;
|
||||
}
|
||||
|
||||
val userFollowsUserIdFkey: Users
|
||||
get(): Users = userFollowsUserIdFkey()
|
||||
|
||||
/**
|
||||
* Get the implicit join path to the <code>public.users</code> table, via
|
||||
* the <code>user_follows_follows_user_id_fkey</code> key.
|
||||
*/
|
||||
fun userFollowsFollowsUserIdFkey(): Users {
|
||||
if (!this::_userFollowsFollowsUserIdFkey.isInitialized)
|
||||
_userFollowsFollowsUserIdFkey = Users(this, USER_FOLLOWS__USER_FOLLOWS_FOLLOWS_USER_ID_FKEY)
|
||||
|
||||
return _userFollowsFollowsUserIdFkey;
|
||||
}
|
||||
|
||||
val userFollowsFollowsUserIdFkey: Users
|
||||
get(): Users = userFollowsFollowsUserIdFkey()
|
||||
override fun `as`(alias: String): UserFollows = UserFollows(DSL.name(alias), this)
|
||||
override fun `as`(alias: Name): UserFollows = UserFollows(alias, this)
|
||||
override fun `as`(alias: Table<*>): UserFollows = UserFollows(alias.getQualifiedName(), this)
|
||||
|
||||
/**
|
||||
* Rename this table
|
||||
*/
|
||||
override fun rename(name: String): UserFollows = UserFollows(DSL.name(name), null)
|
||||
|
||||
/**
|
||||
* Rename this table
|
||||
*/
|
||||
override fun rename(name: Name): UserFollows = UserFollows(name, null)
|
||||
|
||||
/**
|
||||
* Rename this table
|
||||
*/
|
||||
override fun rename(name: Table<*>): UserFollows = UserFollows(name.getQualifiedName(), null)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Row2 type methods
|
||||
// -------------------------------------------------------------------------
|
||||
override fun fieldsRow(): Row2<Long?, Long?> = super.fieldsRow() as Row2<Long?, Long?>
|
||||
|
||||
/**
|
||||
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
|
||||
*/
|
||||
fun <U> mapping(from: (Long?, Long?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
|
||||
|
||||
/**
|
||||
* Convenience mapping calling {@link SelectField#convertFrom(Class,
|
||||
* Function)}.
|
||||
*/
|
||||
fun <U> mapping(toType: Class<U>, from: (Long?, Long?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
|
||||
}
|
||||
@ -17,7 +17,7 @@ import org.jooq.ForeignKey
|
||||
import org.jooq.Name
|
||||
import org.jooq.Record
|
||||
import org.jooq.Records
|
||||
import org.jooq.Row18
|
||||
import org.jooq.Row20
|
||||
import org.jooq.Schema
|
||||
import org.jooq.SelectField
|
||||
import org.jooq.Table
|
||||
@ -153,6 +153,16 @@ open class Users(
|
||||
*/
|
||||
val COUNT_MISS: TableField<UsersRecord, Long?> = createField(DSL.name("count_miss"), SQLDataType.BIGINT, this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.users.is_banned</code>.
|
||||
*/
|
||||
val IS_BANNED: TableField<UsersRecord, Boolean?> = createField(DSL.name("is_banned"), SQLDataType.BOOLEAN.defaultValue(DSL.field(DSL.raw("false"), SQLDataType.BOOLEAN)), this, "")
|
||||
|
||||
/**
|
||||
* The column <code>public.users.approx_ban_date</code>.
|
||||
*/
|
||||
val APPROX_BAN_DATE: TableField<UsersRecord, OffsetDateTime?> = createField(DSL.name("approx_ban_date"), SQLDataType.TIMESTAMPWITHTIMEZONE(6), this, "")
|
||||
|
||||
private constructor(alias: Name, aliased: Table<UsersRecord>?): this(alias, null, null, aliased, null)
|
||||
private constructor(alias: Name, aliased: Table<UsersRecord>?, parameters: Array<Field<*>?>?): this(alias, null, null, aliased, parameters)
|
||||
|
||||
@ -194,18 +204,18 @@ open class Users(
|
||||
override fun rename(name: Table<*>): Users = Users(name.getQualifiedName(), null)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Row18 type methods
|
||||
// Row20 type methods
|
||||
// -------------------------------------------------------------------------
|
||||
override fun fieldsRow(): Row18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?> = super.fieldsRow() as Row18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?>
|
||||
override fun fieldsRow(): Row20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?> = super.fieldsRow() as Row20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?>
|
||||
|
||||
/**
|
||||
* Convenience mapping calling {@link SelectField#convertFrom(Function)}.
|
||||
*/
|
||||
fun <U> mapping(from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
|
||||
fun <U> mapping(from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?) -> U): SelectField<U> = convertFrom(Records.mapping(from))
|
||||
|
||||
/**
|
||||
* Convenience mapping calling {@link SelectField#convertFrom(Class,
|
||||
* Function)}.
|
||||
*/
|
||||
fun <U> mapping(toType: Class<U>, from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
|
||||
fun <U> mapping(toType: Class<U>, from: (Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?) -> U): SelectField<U> = convertFrom(toType, Records.mapping(from))
|
||||
}
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is generated by jOOQ.
|
||||
*/
|
||||
package com.nisemoe.generated.tables.records
|
||||
|
||||
|
||||
import com.nisemoe.generated.tables.UserFollows
|
||||
|
||||
import org.jooq.Field
|
||||
import org.jooq.Record2
|
||||
import org.jooq.Row2
|
||||
import org.jooq.impl.UpdatableRecordImpl
|
||||
|
||||
|
||||
/**
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
open class UserFollowsRecord private constructor() : UpdatableRecordImpl<UserFollowsRecord>(UserFollows.USER_FOLLOWS), Record2<Long?, Long?> {
|
||||
|
||||
open var userId: Long?
|
||||
set(value): Unit = set(0, value)
|
||||
get(): Long? = get(0) as Long?
|
||||
|
||||
open var followsUserId: Long?
|
||||
set(value): Unit = set(1, value)
|
||||
get(): Long? = get(1) as Long?
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Primary key information
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
override fun key(): Record2<Long?, Long?> = super.key() as Record2<Long?, Long?>
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Record2 type implementation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
override fun fieldsRow(): Row2<Long?, Long?> = super.fieldsRow() as Row2<Long?, Long?>
|
||||
override fun valuesRow(): Row2<Long?, Long?> = super.valuesRow() as Row2<Long?, Long?>
|
||||
override fun field1(): Field<Long?> = UserFollows.USER_FOLLOWS.USER_ID
|
||||
override fun field2(): Field<Long?> = UserFollows.USER_FOLLOWS.FOLLOWS_USER_ID
|
||||
override fun component1(): Long? = userId
|
||||
override fun component2(): Long? = followsUserId
|
||||
override fun value1(): Long? = userId
|
||||
override fun value2(): Long? = followsUserId
|
||||
|
||||
override fun value1(value: Long?): UserFollowsRecord {
|
||||
set(0, value)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun value2(value: Long?): UserFollowsRecord {
|
||||
set(1, value)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun values(value1: Long?, value2: Long?): UserFollowsRecord {
|
||||
this.value1(value1)
|
||||
this.value2(value2)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a detached, initialised UserFollowsRecord
|
||||
*/
|
||||
constructor(userId: Long? = null, followsUserId: Long? = null): this() {
|
||||
this.userId = userId
|
||||
this.followsUserId = followsUserId
|
||||
resetChangedOnNotNull()
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,8 @@ import java.time.OffsetDateTime
|
||||
|
||||
import org.jooq.Field
|
||||
import org.jooq.Record1
|
||||
import org.jooq.Record18
|
||||
import org.jooq.Row18
|
||||
import org.jooq.Record20
|
||||
import org.jooq.Row20
|
||||
import org.jooq.impl.UpdatableRecordImpl
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ import org.jooq.impl.UpdatableRecordImpl
|
||||
* This class is generated by jOOQ.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(Users.USERS), Record18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?> {
|
||||
open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(Users.USERS), Record20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?> {
|
||||
|
||||
open var userId: Long?
|
||||
set(value): Unit = set(0, value)
|
||||
@ -96,6 +96,16 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
set(value): Unit = set(17, value)
|
||||
get(): Long? = get(17) as Long?
|
||||
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@set:JvmName("setIsBanned")
|
||||
open var isBanned: Boolean?
|
||||
set(value): Unit = set(18, value)
|
||||
get(): Boolean? = get(18) as Boolean?
|
||||
|
||||
open var approxBanDate: OffsetDateTime?
|
||||
set(value): Unit = set(19, value)
|
||||
get(): OffsetDateTime? = get(19) as OffsetDateTime?
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Primary key information
|
||||
// -------------------------------------------------------------------------
|
||||
@ -103,11 +113,11 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
override fun key(): Record1<Long?> = super.key() as Record1<Long?>
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Record18 type implementation
|
||||
// Record20 type implementation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
override fun fieldsRow(): Row18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?> = super.fieldsRow() as Row18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?>
|
||||
override fun valuesRow(): Row18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?> = super.valuesRow() as Row18<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?>
|
||||
override fun fieldsRow(): Row20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?> = super.fieldsRow() as Row20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?>
|
||||
override fun valuesRow(): Row20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?> = super.valuesRow() as Row20<Long?, String?, LocalDateTime?, String?, Long?, Long?, Double?, Double?, Long?, Long?, Long?, Long?, Long?, Long?, Long?, OffsetDateTime?, Boolean?, Long?, Boolean?, OffsetDateTime?>
|
||||
override fun field1(): Field<Long?> = Users.USERS.USER_ID
|
||||
override fun field2(): Field<String?> = Users.USERS.USERNAME
|
||||
override fun field3(): Field<LocalDateTime?> = Users.USERS.JOIN_DATE
|
||||
@ -126,6 +136,8 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
override fun field16(): Field<OffsetDateTime?> = Users.USERS.SYS_LAST_UPDATE
|
||||
override fun field17(): Field<Boolean?> = Users.USERS.IS_ADMIN
|
||||
override fun field18(): Field<Long?> = Users.USERS.COUNT_MISS
|
||||
override fun field19(): Field<Boolean?> = Users.USERS.IS_BANNED
|
||||
override fun field20(): Field<OffsetDateTime?> = Users.USERS.APPROX_BAN_DATE
|
||||
override fun component1(): Long? = userId
|
||||
override fun component2(): String? = username
|
||||
override fun component3(): LocalDateTime? = joinDate
|
||||
@ -144,6 +156,8 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
override fun component16(): OffsetDateTime? = sysLastUpdate
|
||||
override fun component17(): Boolean? = isAdmin
|
||||
override fun component18(): Long? = countMiss
|
||||
override fun component19(): Boolean? = isBanned
|
||||
override fun component20(): OffsetDateTime? = approxBanDate
|
||||
override fun value1(): Long? = userId
|
||||
override fun value2(): String? = username
|
||||
override fun value3(): LocalDateTime? = joinDate
|
||||
@ -162,6 +176,8 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
override fun value16(): OffsetDateTime? = sysLastUpdate
|
||||
override fun value17(): Boolean? = isAdmin
|
||||
override fun value18(): Long? = countMiss
|
||||
override fun value19(): Boolean? = isBanned
|
||||
override fun value20(): OffsetDateTime? = approxBanDate
|
||||
|
||||
override fun value1(value: Long?): UsersRecord {
|
||||
set(0, value)
|
||||
@ -253,7 +269,17 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
return this
|
||||
}
|
||||
|
||||
override fun values(value1: Long?, value2: String?, value3: LocalDateTime?, value4: String?, value5: Long?, value6: Long?, value7: Double?, value8: Double?, value9: Long?, value10: Long?, value11: Long?, value12: Long?, value13: Long?, value14: Long?, value15: Long?, value16: OffsetDateTime?, value17: Boolean?, value18: Long?): UsersRecord {
|
||||
override fun value19(value: Boolean?): UsersRecord {
|
||||
set(18, value)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun value20(value: OffsetDateTime?): UsersRecord {
|
||||
set(19, value)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun values(value1: Long?, value2: String?, value3: LocalDateTime?, value4: String?, value5: Long?, value6: Long?, value7: Double?, value8: Double?, value9: Long?, value10: Long?, value11: Long?, value12: Long?, value13: Long?, value14: Long?, value15: Long?, value16: OffsetDateTime?, value17: Boolean?, value18: Long?, value19: Boolean?, value20: OffsetDateTime?): UsersRecord {
|
||||
this.value1(value1)
|
||||
this.value2(value2)
|
||||
this.value3(value3)
|
||||
@ -272,13 +298,15 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
this.value16(value16)
|
||||
this.value17(value17)
|
||||
this.value18(value18)
|
||||
this.value19(value19)
|
||||
this.value20(value20)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a detached, initialised UsersRecord
|
||||
*/
|
||||
constructor(userId: Long? = null, username: String? = null, joinDate: LocalDateTime? = null, country: String? = null, countryRank: Long? = null, rank: Long? = null, ppRaw: Double? = null, accuracy: Double? = null, playcount: Long? = null, totalScore: Long? = null, rankedScore: Long? = null, secondsPlayed: Long? = null, count_100: Long? = null, count_300: Long? = null, count_50: Long? = null, sysLastUpdate: OffsetDateTime? = null, isAdmin: Boolean? = null, countMiss: Long? = null): this() {
|
||||
constructor(userId: Long? = null, username: String? = null, joinDate: LocalDateTime? = null, country: String? = null, countryRank: Long? = null, rank: Long? = null, ppRaw: Double? = null, accuracy: Double? = null, playcount: Long? = null, totalScore: Long? = null, rankedScore: Long? = null, secondsPlayed: Long? = null, count_100: Long? = null, count_300: Long? = null, count_50: Long? = null, sysLastUpdate: OffsetDateTime? = null, isAdmin: Boolean? = null, countMiss: Long? = null, isBanned: Boolean? = null, approxBanDate: OffsetDateTime? = null): this() {
|
||||
this.userId = userId
|
||||
this.username = username
|
||||
this.joinDate = joinDate
|
||||
@ -297,6 +325,8 @@ open class UsersRecord private constructor() : UpdatableRecordImpl<UsersRecord>(
|
||||
this.sysLastUpdate = sysLastUpdate
|
||||
this.isAdmin = isAdmin
|
||||
this.countMiss = countMiss
|
||||
this.isBanned = isBanned
|
||||
this.approxBanDate = approxBanDate
|
||||
resetChangedOnNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import com.nisemoe.generated.tables.Scores
|
||||
import com.nisemoe.generated.tables.ScoresJudgements
|
||||
import com.nisemoe.generated.tables.ScoresSimilarity
|
||||
import com.nisemoe.generated.tables.UpdateUserQueue
|
||||
import com.nisemoe.generated.tables.UserFollows
|
||||
import com.nisemoe.generated.tables.UserScores
|
||||
import com.nisemoe.generated.tables.UserScoresSimilarity
|
||||
import com.nisemoe.generated.tables.Users
|
||||
@ -58,6 +59,11 @@ val SCORES_SIMILARITY: ScoresSimilarity = ScoresSimilarity.SCORES_SIMILARITY
|
||||
*/
|
||||
val UPDATE_USER_QUEUE: UpdateUserQueue = UpdateUserQueue.UPDATE_USER_QUEUE
|
||||
|
||||
/**
|
||||
* The table <code>public.user_follows</code>.
|
||||
*/
|
||||
val USER_FOLLOWS: UserFollows = UserFollows.USER_FOLLOWS
|
||||
|
||||
/**
|
||||
* The table <code>public.user_scores</code>.
|
||||
*/
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
package com.nisemoe.nise
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.cache.annotation.EnableCaching
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableCaching
|
||||
@EnableScheduling
|
||||
@EnableRedisIndexedHttpSession(maxInactiveIntervalInSeconds = 2592000)
|
||||
class NiseApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.nisemoe.nise.controller
|
||||
|
||||
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.constraints.Size
|
||||
import org.jooq.DSLContext
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
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.RestController
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@RestController
|
||||
class BanlistController(
|
||||
private val dslContext: DSLContext
|
||||
) {
|
||||
|
||||
data class BanStatisticsResponse(
|
||||
val totalUsersBanned: Int
|
||||
)
|
||||
|
||||
@GetMapping("banlist/statistics")
|
||||
fun getBanStatistics(): BanStatisticsResponse {
|
||||
val totalUsersBanned = dslContext.fetchCount(USERS, USERS.IS_BANNED.eq(true))
|
||||
|
||||
return BanStatisticsResponse(totalUsersBanned)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,157 @@
|
||||
package com.nisemoe.nise.controller
|
||||
|
||||
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.constraints.Size
|
||||
import org.jooq.DSLContext
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
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.RestController
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@RestController
|
||||
class FollowsController(
|
||||
private val dslContext: DSLContext,
|
||||
private val authService: AuthService
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
const val MAX_FOLLOWS_PER_USER = 1000
|
||||
|
||||
}
|
||||
|
||||
data class FollowsBanStatusResponse(
|
||||
val follows: List<FollowsBanStatusEntry>
|
||||
)
|
||||
|
||||
data class FollowsBanStatusEntry(
|
||||
val userId: Long,
|
||||
val username: String,
|
||||
val isBanned: Boolean,
|
||||
val lastUpdate: OffsetDateTime
|
||||
)
|
||||
|
||||
@GetMapping("follows")
|
||||
fun getFollowsBanStatus(): ResponseEntity<FollowsBanStatusResponse> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
val follows = dslContext.select(
|
||||
USERS.USER_ID,
|
||||
USERS.USERNAME,
|
||||
USERS.IS_BANNED,
|
||||
USERS.SYS_LAST_UPDATE
|
||||
)
|
||||
.from(USER_FOLLOWS)
|
||||
.join(USERS).on(USER_FOLLOWS.FOLLOWS_USER_ID.eq(USERS.USER_ID))
|
||||
.where(USER_FOLLOWS.USER_ID.eq(authService.getCurrentUser().userId))
|
||||
.fetch()
|
||||
.map {
|
||||
FollowsBanStatusEntry(
|
||||
it[USERS.USER_ID]!!,
|
||||
it[USERS.USERNAME]!!,
|
||||
it[USERS.IS_BANNED]!!,
|
||||
it[USERS.SYS_LAST_UPDATE]!!
|
||||
)
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(FollowsBanStatusResponse(follows))
|
||||
}
|
||||
|
||||
// TODO: CSRF
|
||||
|
||||
@GetMapping("follows/{followsUserId}")
|
||||
fun getFollowsBanStatusByUserId(@PathVariable followsUserId: Long): ResponseEntity<FollowsBanStatusEntry> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
val userId = authService.getCurrentUser().userId
|
||||
|
||||
val follows = dslContext.select(
|
||||
USERS.USER_ID,
|
||||
USERS.USERNAME,
|
||||
USERS.IS_BANNED,
|
||||
USERS.SYS_LAST_UPDATE
|
||||
)
|
||||
.from(USER_FOLLOWS)
|
||||
.join(USERS).on(USER_FOLLOWS.FOLLOWS_USER_ID.eq(USERS.USER_ID))
|
||||
.where(USER_FOLLOWS.USER_ID.eq(userId).and(USER_FOLLOWS.FOLLOWS_USER_ID.eq(followsUserId)))
|
||||
.fetch()
|
||||
.map {
|
||||
FollowsBanStatusEntry(
|
||||
it[USERS.USER_ID]!!,
|
||||
it[USERS.USERNAME]!!,
|
||||
it[USERS.IS_BANNED]!!,
|
||||
it[USERS.SYS_LAST_UPDATE]!!
|
||||
)
|
||||
}
|
||||
|
||||
return if(follows.isEmpty()) {
|
||||
ResponseEntity.status(404).build()
|
||||
} else {
|
||||
ResponseEntity.ok(follows.first())
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateFollowsBanStatusRequest(
|
||||
@Valid @field:Size(max = 200)
|
||||
val userIds: List<Long>,
|
||||
)
|
||||
|
||||
@PutMapping("follows")
|
||||
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
|
||||
if(dslContext.fetchCount(USER_FOLLOWS, USER_FOLLOWS.USER_ID.eq(authService.getCurrentUser().userId)) >= MAX_FOLLOWS_PER_USER) {
|
||||
return ResponseEntity.status(400).build()
|
||||
}
|
||||
|
||||
val userId = authService.getCurrentUser().userId
|
||||
|
||||
for(userIdToBan in request.userIds) {
|
||||
dslContext.insertInto(USER_FOLLOWS)
|
||||
.columns(USER_FOLLOWS.USER_ID, USER_FOLLOWS.FOLLOWS_USER_ID)
|
||||
.values(userId, userIdToBan)
|
||||
.onDuplicateKeyIgnore()
|
||||
.execute()
|
||||
}
|
||||
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
|
||||
data class DeleteFollowsBanStatusRequest(
|
||||
@Valid @field:Size(max = 200)
|
||||
val userIds: List<Long>,
|
||||
)
|
||||
|
||||
@DeleteMapping("follows")
|
||||
fun deleteFollowsBanStatus(@RequestBody @Valid request: DeleteFollowsBanStatusRequest): ResponseEntity<Void> {
|
||||
if(!authService.isLoggedIn()) {
|
||||
return ResponseEntity.status(401).build()
|
||||
}
|
||||
|
||||
val userId = authService.getCurrentUser().userId
|
||||
|
||||
for(userIdToUnban in request.userIds) {
|
||||
dslContext.deleteFrom(USER_FOLLOWS)
|
||||
.where(USER_FOLLOWS.USER_ID.eq(userId))
|
||||
.and(USER_FOLLOWS.FOLLOWS_USER_ID.eq(userIdToUnban))
|
||||
.execute()
|
||||
}
|
||||
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
|
||||
}
|
||||
@ -532,7 +532,7 @@ class ScoreService(
|
||||
error = it.error!!,
|
||||
distanceToCenter = it.distanceCenter!!,
|
||||
distanceToEdge = it.distanceEdge!!,
|
||||
time = it.time!!,
|
||||
time = it.time!!.toInt(),
|
||||
type = mapLegacyJudgement(it.type!!)
|
||||
)
|
||||
}
|
||||
|
||||
@ -348,6 +348,11 @@ class ImportScores(
|
||||
.set(SCORES.IS_BANNED, true)
|
||||
.where(SCORES.USER_ID.eq(userId))
|
||||
.execute()
|
||||
dslContext.update(USERS)
|
||||
.set(USERS.IS_BANNED, true)
|
||||
.set(USERS.APPROX_BAN_DATE, OffsetDateTime.now())
|
||||
.where(USERS.USER_ID.eq(userId))
|
||||
.execute()
|
||||
this.logger.info("User $userId is banned.")
|
||||
}
|
||||
Thread.sleep(SLEEP_AFTER_API_CALL)
|
||||
@ -724,7 +729,7 @@ class ImportScores(
|
||||
replayData = scoreReplay.content, beatmapData = beatmapFile, mods = Mod.combineModStrings(score.mods)
|
||||
).get()
|
||||
} catch (e: Exception) {
|
||||
this.logger.error("Circleguard failed to process replay with score_id: ${score.id}")
|
||||
this.logger.error("Circleguard failed to process replay with score_id: ${score.id}", e)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -107,6 +107,11 @@ class ImportUsers(
|
||||
.set(SCORES.IS_BANNED, true)
|
||||
.where(SCORES.USER_ID.eq(missingId))
|
||||
.execute()
|
||||
dslContext.update(USERS)
|
||||
.set(USERS.IS_BANNED, true)
|
||||
.set(USERS.APPROX_BAN_DATE, OffsetDateTime.now())
|
||||
.where(USERS.USER_ID.eq(missingId))
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,10 @@ spring.data.redis.port=${REDIS_PORT:6379}
|
||||
spring.data.redis.repositories.enabled=false
|
||||
spring.data.redis.database=${REDIS_DB:2}
|
||||
|
||||
# session
|
||||
server.servlet.session.timeout=2592000
|
||||
spring.session.store-type=redis
|
||||
|
||||
# osu!auth
|
||||
|
||||
spring.security.oauth2.client.registration.osu.clientId=${OSU_CLIENT_ID}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
alter table public.users
|
||||
add column is_banned boolean default false,
|
||||
add column approx_ban_date timestamp with time zone;
|
||||
|
||||
update public.users
|
||||
set is_banned = true,
|
||||
approx_ban_date = now()
|
||||
from (select distinct user_id
|
||||
from scores
|
||||
where is_banned = true) banned_users
|
||||
where public.users.user_id = banned_users.user_id;
|
||||
@ -0,0 +1,8 @@
|
||||
create table public.user_follows (
|
||||
user_id bigserial not null,
|
||||
follows_user_id bigserial not null,
|
||||
|
||||
primary key (user_id, follows_user_id),
|
||||
foreign key (user_id) references public.users (user_id),
|
||||
foreign key (follows_user_id) references public.users (user_id)
|
||||
);
|
||||
@ -8,6 +8,7 @@ import {ViewUserComponent} from "./view-user/view-user.component";
|
||||
import {ViewReplayPairComponent} from "./view-replay-pair/view-replay-pair.component";
|
||||
import {SearchComponent} from "./search/search.component";
|
||||
import {ContributeComponent} from "./contribute/contribute.component";
|
||||
import {BanlistComponent} from "./banlist/banlist.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: 'sus/:f', component: ViewSuspiciousScoresComponent, title: '/sus/'},
|
||||
@ -23,6 +24,7 @@ const routes: Routes = [
|
||||
|
||||
{path: 'p/:replay1Id/:replay2Id', component: ViewReplayPairComponent},
|
||||
|
||||
{path: 'banlist', component: BanlistComponent, title: '/ban/'},
|
||||
{path: 'contribute', component: ContributeComponent, title: '/contribute/ <3'},
|
||||
{path: '**', component: HomeComponent, title: '/nise.moe/'},
|
||||
];
|
||||
|
||||
@ -7,3 +7,11 @@
|
||||
overflow: hidden;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.link-pink {
|
||||
color: #dd8fdcf7
|
||||
}
|
||||
|
||||
.link-pink:hover {
|
||||
color: rgba(234, 78, 179, 0.97)
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<li><a [routerLink]="['/sus']">./suspicious-scores</a></li>
|
||||
<li><a [routerLink]="['/stolen']">./stolen-replays</a></li>
|
||||
<li><a [routerLink]="['/search']">./advanced-search</a></li>
|
||||
<li><a style="color: #dd8fdcf7" [routerLink]="['/contribute']">./contribute <3</a></li>
|
||||
<li><a class="link-pink" [routerLink]="['/contribute']">./contribute <3</a></li>
|
||||
</ul>
|
||||
<form (ngSubmit)="onSubmit()">
|
||||
<input style="width: 100%" type="text" [(ngModel)]="term" [ngModelOptions]="{standalone: true}" id="nise-osu-username" required minlength="2" maxlength="50" placeholder="Search for users...">
|
||||
|
||||
3
nise-frontend/src/app/banlist/banlist.component.css
Normal file
3
nise-frontend/src/app/banlist/banlist.component.css
Normal file
@ -0,0 +1,3 @@
|
||||
table td {
|
||||
text-align: center;
|
||||
}
|
||||
31
nise-frontend/src/app/banlist/banlist.component.html
Normal file
31
nise-frontend/src/app/banlist/banlist.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>
|
||||
61
nise-frontend/src/app/banlist/banlist.component.ts
Normal file
61
nise-frontend/src/app/banlist/banlist.component.ts
Normal file
@ -0,0 +1,61 @@
|
||||
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 BanStatisticsResponse {
|
||||
totalUsersBanned: number;
|
||||
}
|
||||
|
||||
interface FollowsBanStatusResponse {
|
||||
follows: FollowsBanStatusEntry[];
|
||||
}
|
||||
|
||||
interface FollowsBanStatusEntry {
|
||||
userId: number;
|
||||
username: string;
|
||||
isBanned: boolean;
|
||||
lastUpdate: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-banlist',
|
||||
standalone: true,
|
||||
imports: [
|
||||
JsonPipe,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './banlist.component.html',
|
||||
styleUrl: './banlist.component.css'
|
||||
})
|
||||
export class BanlistComponent implements OnInit {
|
||||
|
||||
banStatistics: BanStatisticsResponse | null = null
|
||||
follows: FollowsBanStatusResponse | null = null;
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getBanStatistics();
|
||||
this.getFollows();
|
||||
}
|
||||
|
||||
getBanStatistics() {
|
||||
this.httpClient.get<BanStatisticsResponse>(`${environment.apiUrl}/banlist/statistics`).subscribe(response => {
|
||||
this.banStatistics = response;
|
||||
});
|
||||
}
|
||||
|
||||
getFollows(): void {
|
||||
this.httpClient.get<FollowsBanStatusResponse>(`${environment.apiUrl}/follows`).subscribe(response => {
|
||||
this.follows = response;
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly calculateTimeAgo = calculateTimeAgo;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import {ReplayData} from "./replays";
|
||||
import {differenceInDays, differenceInHours} from "date-fns/fp";
|
||||
|
||||
export function formatDuration(seconds: number): string | null {
|
||||
if(!seconds) {
|
||||
@ -50,3 +51,24 @@ export function calculateAccuracy(replayData: ReplayData): number {
|
||||
const accuracy = (300 * hit300 + 100 * hit100 + 50 * hit50) / (300 * totalHits);
|
||||
return accuracy * 100;
|
||||
}
|
||||
|
||||
export function calculateTimeAgo(dateStr: string): string {
|
||||
const inputDate = new Date(dateStr);
|
||||
const now = new Date();
|
||||
|
||||
if (isNaN(inputDate.getTime())) {
|
||||
return "???";
|
||||
}
|
||||
|
||||
const difference = Math.abs(differenceInHours(now, inputDate));
|
||||
|
||||
if (difference < 1) {
|
||||
return "recently";
|
||||
} else if (difference < 24) {
|
||||
return `${difference}h ago`;
|
||||
} else {
|
||||
const days = Math.abs(differenceInDays(now, inputDate));
|
||||
const hours = difference % 24;
|
||||
return `${days}d ${hours}h ago`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,11 @@
|
||||
<h1>
|
||||
<img [src]="'https://a.ppy.sh/' + this.userInfo.user_details.user_id" class="avatar">
|
||||
{{ this.userInfo.user_details.username }}
|
||||
|
||||
<ng-container *ngIf="this.userService.isUserLoggedIn() && this.isFollowing != null">
|
||||
<button *ngIf="!this.followService.userFollowStatus.get(this.userInfo.user_details.user_id)" (click)="this.followService.addNewUserFollow(this.userInfo.user_details.user_id)" class="btn btn-outline-secondary btn-sm">(+) Add Follow</button>
|
||||
<button *ngIf="this.followService.userFollowStatus.get(this.userInfo.user_details.user_id)" (click)="this.followService.removeUserFollow(this.userInfo.user_details.user_id)" class="btn btn-outline-secondary btn-sm">(-) Remove follow</button>
|
||||
</ng-container>
|
||||
</h1>
|
||||
|
||||
<div class="mb-2 mt-2 btn-group">
|
||||
@ -51,7 +56,7 @@
|
||||
<span class="btn-warning">wait a bit to force update</span>
|
||||
</ng-container>
|
||||
<span style="margin-left: 4px">|</span>
|
||||
last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? this.calculateTimeAgo(this.userInfo.queue_details.lastCompletedUpdate) : 'never'}}
|
||||
last update: {{ this.userInfo.queue_details.lastCompletedUpdate ? calculateTimeAgo(this.userInfo.queue_details.lastCompletedUpdate) : 'never'}}
|
||||
</ng-container>
|
||||
<ng-template #updateProgress>
|
||||
<div class="progress">
|
||||
|
||||
@ -6,14 +6,14 @@ import {environment} from "../../environments/environment";
|
||||
import {DecimalPipe, JsonPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common";
|
||||
import {ActivatedRoute, RouterLink} from "@angular/router";
|
||||
import {UserDetails, UserQueueDetails} from "../userDetails";
|
||||
import {countryCodeToFlag, formatDuration} from "../format";
|
||||
import {calculateTimeAgo, 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";
|
||||
import {differenceInDays, differenceInHours} from "date-fns/fp";
|
||||
import {FilterManagerService} from "../filter-manager.service";
|
||||
import {UserService} from "../../corelib/service/user.service";
|
||||
import {FollowService} from "../../corelib/service/follow.service";
|
||||
|
||||
interface UserInfo {
|
||||
user_details: UserDetails;
|
||||
@ -49,6 +49,7 @@ interface UserScoresFilter {
|
||||
})
|
||||
export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
isFollowing: boolean | null = null;
|
||||
isLoading = false;
|
||||
notFound = false;
|
||||
userId: string | null = null;
|
||||
@ -64,7 +65,8 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private title: Title,
|
||||
private rxStompService: RxStompService,
|
||||
public userService: UserService
|
||||
public userService: UserService,
|
||||
public followService: FollowService
|
||||
) { }
|
||||
|
||||
getUserInfo(): Observable<UserInfo> {
|
||||
@ -93,6 +95,14 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private checkIfUserIsFollowed(): void {
|
||||
if(this.userService.isUserLoggedIn()) {
|
||||
this.followService.checkIfUserIsFollowed(this.userInfo!.user_details.user_id).then(isFollowing => {
|
||||
this.isFollowing = isFollowing;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private loadUser(isScoreUpdate = false) {
|
||||
this.getUserInfo().pipe(
|
||||
catchError(error => {
|
||||
@ -121,6 +131,7 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.title.setTitle(`${this.userInfo.user_details.username}`);
|
||||
this.subscribeToUser();
|
||||
}
|
||||
this.checkIfUserIsFollowed();
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -129,27 +140,6 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.liveUserSub?.unsubscribe();
|
||||
}
|
||||
|
||||
calculateTimeAgo(dateStr: string): string {
|
||||
const inputDate = new Date(dateStr);
|
||||
const now = new Date();
|
||||
|
||||
if (isNaN(inputDate.getTime())) {
|
||||
return "???";
|
||||
}
|
||||
|
||||
const difference = Math.abs(differenceInHours(now, inputDate));
|
||||
|
||||
if (difference < 1) {
|
||||
return "recently";
|
||||
} else if (difference < 24) {
|
||||
return `${difference}h ago`;
|
||||
} else {
|
||||
const days = Math.abs(differenceInDays(now, inputDate));
|
||||
const hours = difference % 24;
|
||||
return `${days}d ${hours}h ago`;
|
||||
}
|
||||
}
|
||||
|
||||
addUserToQueue(): void {
|
||||
const body = {
|
||||
userId: this.userInfo?.user_details.user_id
|
||||
@ -202,6 +192,6 @@ export class ViewUserComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
protected readonly formatDuration = formatDuration;
|
||||
protected readonly countryCodeToFlag = countryCodeToFlag;
|
||||
|
||||
protected readonly calculateTimeAgo = calculateTimeAgo;
|
||||
|
||||
}
|
||||
|
||||
86
nise-frontend/src/corelib/service/follow.service.ts
Normal file
86
nise-frontend/src/corelib/service/follow.service.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {UserService} from "./user.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FollowService {
|
||||
|
||||
userFollowStatus: Map<number, boolean> = new Map<number, boolean>();
|
||||
|
||||
constructor(
|
||||
private userService: UserService,
|
||||
private httpClient: HttpClient
|
||||
) { }
|
||||
|
||||
removeUserFollow(userId: number): Promise<boolean> {
|
||||
if(!this.userService.isUserLoggedIn()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const body = {
|
||||
userIds: [userId]
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.httpClient.delete(`${environment.apiUrl}/follows`, { body: body })
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.userFollowStatus.set(userId, false);
|
||||
resolve(true);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addNewUserFollow(userId: number): Promise<boolean> {
|
||||
if(!this.userService.isUserLoggedIn()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const body = {
|
||||
userIds: [userId]
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.httpClient.put(`${environment.apiUrl}/follows`, body)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.userFollowStatus.set(userId, true);
|
||||
resolve(true);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
checkIfUserIsFollowed(userId: number): Promise<boolean> {
|
||||
if(!this.userService.isUserLoggedIn()) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
this.httpClient.get<boolean>(`${environment.apiUrl}/follows/${userId}`)
|
||||
.subscribe({
|
||||
next: (isFollowing) => {
|
||||
this.userFollowStatus.set(userId, isFollowing);
|
||||
resolve(isFollowing);
|
||||
},
|
||||
error: (err) => {
|
||||
if (err.status === 404) {
|
||||
this.userFollowStatus.set(userId, false);
|
||||
resolve(false);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user