Skip to content

Commit

Permalink
1.4.0
Browse files Browse the repository at this point in the history
* Make enrollee description consistent
* Add pingdom and statuspage instrumentation
* Add Pingdom uptime report
* Clean up help page
* Add option to load all challenges
* Add images to help page
* Add support for removig student from class
* Move data from redis to postgres
  • Loading branch information
pambrose authored Sep 30, 2020
1 parent 1cc52a6 commit 4f409fb
Show file tree
Hide file tree
Showing 86 changed files with 3,939 additions and 1,954 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ cc:
run:
./gradlew run

local-reset:
cd flyway; make local-reset

do-reset:
cd flyway; make do-reset

tests:
./gradlew check

Expand Down
13 changes: 11 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ plugins {
id 'java'
id 'application'
id 'maven' // Required for jitpack.io to do a ./gradlew install
id 'org.jetbrains.kotlin.jvm' version '1.4.0'
id "com.github.ben-manes.versions" version '0.29.0'
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
id "com.github.ben-manes.versions" version '0.33.0'
id 'com.github.johnrengelman.shadow' version '6.0.0'
}

Expand Down Expand Up @@ -73,6 +73,15 @@ dependencies {

implementation "redis.clients:jedis:$redis_version"

implementation "com.zaxxer:HikariCP:$hikari_version"

implementation "org.jetbrains.exposed:exposed-core:$exposed_version"
implementation "org.jetbrains.exposed:exposed-dao:$exposed_version"
implementation "org.jetbrains.exposed:exposed-jdbc:$exposed_version"
implementation "org.jetbrains.exposed:exposed-jodatime:$exposed_version"

implementation "com.impossibl.pgjdbc-ng:pgjdbc-ng-all:$postgres_version"

implementation "com.google.code.gson:gson:$gson_version"
implementation "com.google.guava:guava:$guava_version"

Expand Down
16 changes: 15 additions & 1 deletion docs/notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,18 @@ Setup:
* sdk install java
* sdk install kotlin
* sdk install gradle
* docker login
* docker login

## Postgres
* https://hackernoon.com/dont-install-postgres-docker-pull-postgres-bee20e200198
* `mkdir -p $HOME/docker/volumes/postgres`
* `docker run --rm --name pg-docker -e POSTGRES_PASSWORD=docker -d -p 5432:5432 -v $HOME/docker/volumes/postgres:/var/lib/postgresql/data postgres`
* `psql -h localhost -U postgres -d postgres` use password: docker

## Postgres Connection Pools
* https://www.thebookofjoel.com/kotlin-ktor-exposed-postgres
* https://github.com/brettwooldridge/HikariCP

## Exposed
* Upsert: https://github.com/JetBrains/Exposed/issues/167
* Upsert: https://medium.com/@OhadShai/first-steps-with-kotlin-exposed-cb361a9bf5ac
19 changes: 19 additions & 0 deletions flyway/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

local-reset: local-clean local-migrate

local-clean:
flyway -locations="filesystem:../src/main/resources/sql" -user=postgres -password=docker -url=jdbc:postgresql://localhost:5432/readingbat clean

local-migrate:
flyway -locations="filesystem:../src/main/resources/sql" -user=postgres -password=docker -url=jdbc:postgresql://localhost:5432/readingbat migrate


do-reset: do-clean do-migrate

do-clean:
flyway -locations="filesystem:../src/main/resources/sql" -user=readingbat -password=zzz -url=jdbc:postgresql://readingbat-postgres-do-user-329986-0.b.db.ondigitalocean.com:25060/readingbat clean

do-migrate:
flyway -locations="filesystem:../src/main/resources/sql" -user=readingbat -password=zzz -url=jdbc:postgresql://readingbat-postgres-do-user-329986-0.b.db.ondigitalocean.com:25060/readingbat migrate


17 changes: 10 additions & 7 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ org.gradle.parallel=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
commons_version=1.9
css_version=1.0.0-pre.112-kotlin-1.4.0
css_version=1.0.0-pre.122-kotlin-1.4.10
exposed_version=0.27.1
flexmark_version=0.62.2
github_api_version=1.116
gson_version=2.8.6
guava_version=29.0-jre
hikari_version=3.4.5
java_script_version=2.0.0
junit_version=5.6.1
junit_version=5.7.0
kluent_version=1.61
ktor_version=1.4.0
ktor_version=1.4.1
logback_version=1.2.3
logging_version=1.8.3
logging_version=2.0.3
postgres_version=0.8.4
prometheus_version=0.9.0
proxy_version=1.8.5
proxy_version=d38ab96
redis_version=3.3.0
#selenium_version=4.0.0-alpha-6
sendgrid_version=4.6.4
utils_version=8a9b883
sendgrid_version=4.6.5
utils_version=10f4e7c
140 changes: 93 additions & 47 deletions src/main/kotlin/com/github/readingbat/common/ClassCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,73 +17,119 @@

package com.github.readingbat.common

import com.github.pambrose.common.util.isNotNull
import com.github.pambrose.common.util.isNull
import com.github.pambrose.common.util.randomId
import com.github.pambrose.common.util.toDoubleQuoted
import com.github.readingbat.common.CommonUtils.keyOf
import com.github.readingbat.common.FormFields.DISABLED_MODE
import com.github.readingbat.common.KeyConstants.CLASS_CODE_KEY
import com.github.readingbat.common.KeyConstants.CLASS_INFO_KEY
import com.github.readingbat.common.KeyConstants.DESC_FIELD
import com.github.readingbat.common.KeyConstants.TEACHER_FIELD
import com.github.readingbat.common.User.Companion.toUser
import com.github.readingbat.server.keyOf
import com.github.readingbat.dsl.InvalidConfigurationException
import com.github.readingbat.server.Classes
import com.github.readingbat.server.Enrollees
import com.github.readingbat.server.Users
import com.github.readingbat.server.get
import io.ktor.http.*
import redis.clients.jedis.Jedis
import redis.clients.jedis.Transaction
import mu.KLogging
import org.jetbrains.exposed.sql.Count
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.time.measureTimedValue

internal data class ClassCode(val value: String) {
val isNotEnabled by lazy { value == DISABLED_MODE || value.isBlank() }
val isEnabled by lazy { !isNotEnabled }
val classCodeEnrollmentKey by lazy { keyOf(CLASS_CODE_KEY, value) }
val classInfoKey by lazy { keyOf(CLASS_INFO_KEY, value) }
val displayedValue get() = if (value == DISABLED_MODE) "" else value

fun isValid(redis: Jedis) = redis.exists(classCodeEnrollmentKey) ?: false

fun isNotValid(redis: Jedis) = !isValid(redis)
val displayedValue get() = if (value == DISABLED_MODE) "" else value

fun fetchEnrollees(redis: Jedis?): List<User> =
if (redis.isNull() || isNotEnabled)
private val classCodeDbmsId
get() =
measureTimedValue {
transaction {
Classes
.slice(Classes.id)
.select { Classes.classCode eq value }
.map { it[Classes.id].value }
.firstOrNull() ?: throw InvalidConfigurationException("Missing class code $value")
}
}.let {
logger.info { "Looked up classId in ${it.duration}" }
it.value
}

fun isNotValid() = !isValid()

fun isValid() =
transaction {
Classes
.slice(Count(Classes.classCode))
.select { Classes.classCode eq value }
.map { it[0] as Long }
.first().also { logger.info { "ClassCode.isValid() returned $it for $value" } } > 0
}

fun fetchEnrollees(): List<User> =
if (isNotEnabled)
emptyList()
else
(redis.smembers(classCodeEnrollmentKey) ?: emptySet())
.filter { it.isNotEmpty() }
.map { it.toUser(null) }

fun addEnrolleePlaceholder(tx: Transaction) {
tx.sadd(classCodeEnrollmentKey, "")
}

fun addEnrollee(user: User, tx: Transaction) {
tx.sadd(classCodeEnrollmentKey, user.id)
}

fun removeEnrollee(user: User, tx: Transaction) {
tx.srem(classCodeEnrollmentKey, user.id)
}

fun deleteAllEnrollees(tx: Transaction) {
tx.del(classCodeEnrollmentKey)
}

fun initializeWith(classDesc: String, user: User, tx: Transaction) {
tx.hset(classInfoKey, mapOf(DESC_FIELD to classDesc, TEACHER_FIELD to user.id))
}

fun fetchClassDesc(redis: Jedis, quoted: Boolean = false) =
(redis.hget(classInfoKey, DESC_FIELD) ?: "Missing description").let { if (quoted) it.toDoubleQuoted() else it }

fun toDisplayString(redis: Jedis?): String {
val classDesc = if (redis.isNotNull()) fetchClassDesc(redis, true) else "Description unavailable"
return "$classDesc [$value]"
}

fun fetchClassTeacherId(redis: Jedis) = redis.hget(classInfoKey, TEACHER_FIELD) ?: ""
transaction {
val userIds =
(Classes innerJoin Enrollees)
.slice(Enrollees.userRef)
.select { Classes.classCode eq value }
.map { it[0] as Long }

Users
.slice(Users.userId)
.select { Users.id inList userIds }
.map { (it[0] as String).toUser(null) }
.also { logger.info { "fetchEnrollees() returning $it" } }
}

fun deleteClassCode() = Classes.deleteWhere { Classes.classCode eq value }

fun addEnrollee(user: User) =
transaction {
Enrollees
.insert { row ->
row[classesRef] = classCodeDbmsId
row[userRef] = user.userDbmsId
}
}

fun removeEnrollee(user: User) =
Enrollees.deleteWhere { (Enrollees.classesRef eq classCodeDbmsId) and (Enrollees.userRef eq user.userDbmsId) }


fun fetchClassDesc(quoted: Boolean = false) =
transaction {
(Classes
.slice(Classes.description)
.select { Classes.classCode eq value }
.map { it[0] as String }
.firstOrNull() ?: "Missing description")
.also { logger.info { "fetchClassDesc() returned ${it.toDoubleQuoted()} for $value" } }
}.let { if (quoted) it.toDoubleQuoted() else it }

fun toDisplayString() = "${fetchClassDesc(true)} [$value]"

fun fetchClassTeacherId() =
transaction {
((Classes innerJoin Users)
.slice(Users.userId)
.select { Classes.classCode eq value }
.map { it[0] as String }
.firstOrNull() ?: "").also { logger.info { "fetchClassTeacherId() returned $it" } }
}

override fun toString() = value

companion object {
companion object : KLogging() {
internal val DISABLED_CLASS_CODE = ClassCode(DISABLED_MODE)

internal fun newClassCode() = ClassCode(randomId(15))
Expand Down
13 changes: 9 additions & 4 deletions src/main/kotlin/com/github/readingbat/common/CommonUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
package com.github.readingbat.common

import com.github.pambrose.common.util.join
import com.github.pambrose.common.util.md5

internal object CommonUtils {

internal fun pathOf(vararg elems: Any): String = elems.toList().map { it.toString() }.join("/")
fun keyOf(vararg keys: Any) = keys.joinToString(KeyConstants.KEY_SEP) { it.toString() }

internal fun String.maskUrl() =
fun md5Of(vararg keys: Any) = keys.joinToString(KeyConstants.KEY_SEP) { it.toString() }.md5()

fun pathOf(vararg elems: Any): String = elems.toList().map { it.toString() }.join("/")

fun String.maskUrl() =
if ("://" in this && "@" in this) {
val scheme = split("://")
val uri = split("@")
Expand All @@ -33,6 +38,6 @@ internal object CommonUtils {
this
}

internal fun String.obfuscate(freq: Int = 2) =
fun String.obfuscate(freq: Int = 2) =
mapIndexed { i, v -> if (i % freq == 0) '*' else v }.joinToString("")
}
}
16 changes: 13 additions & 3 deletions src/main/kotlin/com/github/readingbat/common/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ internal object Constants {
const val SESSION_ID = "sessionid"
const val ICONS = "icons"
const val MSG = "msg"
const val BACK_PATH = "backPath"
const val STATIC = "static"
const val PRISM = "prism"
const val RESP = "response"
Expand All @@ -49,6 +48,9 @@ internal object Constants {
const val PROCESS_USER_ANSWERS_JS_FUNC = "processUserAnswers"
const val LIKE_DISLIKE_JS_FUNC = "likeDislike"
const val NO_TRACK_HEADER = "NO_TRACK"
const val UNKNOWN = "Unknown"
const val UNASSIGNED = "unassigned"
const val REDIS_IS_DOWN = "Redis is down"

const val CLASS_CODE_QP = "class-code"
const val USER_ID_QP = "user-id"
Expand All @@ -64,6 +66,7 @@ internal object Constants {

internal object PropertyNames {
internal const val READINGBAT = "readingbat"
internal const val DBMS = "dbms"
internal const val SITE = "site"
internal const val AGENT = "agent"
internal const val CLASSES = "classes"
Expand All @@ -78,6 +81,7 @@ internal object Endpoints {
const val STATIC_ROOT = "https://static.readingbat.com"
const val CHALLENGE_ROOT = "/content"
const val PLAYGROUND_ROOT = "/playground"
const val ADMIN_PREFS_ENDPOINT = "/admin-prefs"
const val USER_PREFS_ENDPOINT = "/user-prefs"
const val TEACHER_PREFS_ENDPOINT = "/teacher-prefs"
const val CLASS_SUMMARY_ENDPOINT = "/class-summary"
Expand Down Expand Up @@ -109,6 +113,7 @@ internal object Endpoints {
const val LOAD_JAVA_ENDPOINT = "/load-java"
const val LOAD_PYTHON_ENDPOINT = "/load-python"
const val LOAD_KOTLIN_ENDPOINT = "/load-kotlin"
const val LOAD_ALL_ENDPOINT = "/load-all"

const val DELETE_CONTENT_IN_REDIS_ENDPOINT = "/clear-caches"

Expand All @@ -126,7 +131,7 @@ internal object Endpoints {
"${classSummaryEndpoint(classCode)}&$LANG_TYPE_QP=$languageName&$GROUP_NAME_QP=$groupName"

fun studentSummaryEndpoint(classCode: ClassCode, languageName: LanguageName, student: User) =
"$STUDENT_SUMMARY_ENDPOINT?$CLASS_CODE_QP=$classCode&$LANG_TYPE_QP=$languageName&$USER_ID_QP=${student.id}"
"$STUDENT_SUMMARY_ENDPOINT?$CLASS_CODE_QP=$classCode&$LANG_TYPE_QP=$languageName&$USER_ID_QP=${student.userId}"
}

internal object StaticFileNames {
Expand Down Expand Up @@ -164,15 +169,17 @@ internal object FormFields {
const val CLASS_CODE_NAME_PARAM = "class_code"
const val CLASS_DESC_PARAM = "class_desc"
const val CLASS_CODE_CHOICE_PARAM = "class_code_choice"
const val DEFAULT_LANGUAGE_CHOICE_PARAM = "default_language_choice"
const val CHOICE_SOURCE_PARAM = "choice_source"
const val TEACHER_PREF = "teacher_prefs"
const val CLASS_SUMMARY = "class_summary"
const val DISABLED_MODE = "classes_disabled"
const val CURR_PASSWORD_PARAM = "curr_passwd"
const val NEW_PASSWORD_PARAM = "new_passwd"
const val USER_PREFS_ACTION_PARAM = "pref_action"
const val PREFS_ACTION_PARAM = "pref_action"
const val RESET_ID_PARAM = "reset_id"
const val ADMIN_ACTION_PARAM = "admin_action"
const val USER_ID_PARAM = "user_id"

const val LANGUAGE_NAME_PARAM = "language_name_key"
const val GROUP_NAME_PARAM = "group_name_key"
Expand All @@ -184,9 +191,12 @@ internal object FormFields {
const val JOIN_CLASS = "Join Class"
const val CREATE_CLASS = "Create Class"
const val DELETE_CLASS = "Delete Class"
const val MAKE_ACTIVE_CLASS = "Make Active Class"
const val UPDATE_ACTIVE_CLASS = "Update Active Class"
const val UPDATE_DEFAULT_LANGUAGE = "Update Default Language"
const val NO_ACTIVE_CLASS = "No active class"
const val JOIN_A_CLASS = "Join a Class"
const val REMOVE_FROM_CLASS = "Remove From Class"
const val WITHDRAW_FROM_CLASS = "Withdraw From Class"
const val DELETE_ACCOUNT = "Delete Account"
const val DELETE_ALL_DATA = "Delete All Data"
Expand Down
Loading

0 comments on commit 4f409fb

Please sign in to comment.