From cca7e3cf01ba5c3024dea37d139842f71d8aacd9 Mon Sep 17 00:00:00 2001 From: Khaled Trabelsi <131378411+trKhaled@users.noreply.github.com> Date: Wed, 22 Nov 2023 07:20:35 +0100 Subject: [PATCH 1/5] 1483 api sharing of sql playground copy (#1524) * added routing for dump service; added token generation for expiry and scheduled cleanup * updated the controller logic * added share playground args to runner * added runner type and share vertical for testing --- .../fbs/controller/v2/PlaygroundController.kt | 7 +++ .../fbs/controller/v2/RunnerApiController.kt | 6 +-- .../thm/ii/fbs/model/v2/checker/RunnerType.kt | 3 +- .../v2/checker/SharePlaygroundArguments.kt | 12 ++++++ .../model/v2/security/SharePlaygroundToken.kt | 14 ++++++ .../v2/checker/SqlPlaygroundCheckerService.kt | 24 ++++++++--- .../SharePlaygroundTokenRepository.kt | 9 ++++ .../SharePlaygroundCleanupService.kt | 25 +++++++++++ .../runner/SQLPlaygroundService.scala | 7 ++- .../ii/fbs/types/SharePlaygroundArgs.scala | 7 +++ .../thm/ii/fbs/verticles/HttpVerticle.scala | 5 ++- .../runner/SharePlaygroundVerticle.scala | 43 +++++++++++++++++++ 12 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt create mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala create mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt index a65262dee..3246cf947 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt @@ -72,6 +72,13 @@ class PlaygroundController( return databaseRepository.save(db) } + @PostMapping("/{dbId}/dump") + @ResponseBody + fun createSharePlayground(@CurrentToken currentToken: LegacyToken, @PathVariable("dbId") dbId: Int): String { + val currentActiveDb = databaseRepository.findByOwner_IdAndIdAndDeleted(currentToken.id, dbId, false) ?: throw NotFoundException() + return sqlPlaygroundCheckerService.createSharePlayground(currentActiveDb) + } + @PostMapping("/{dbId}/reset") @ResponseBody @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt index 6a2c5d07c..2d9f9d9c3 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt @@ -11,10 +11,7 @@ import de.thm.ii.fbs.services.v2.persistence.SqlPlaygroundQueryRepository import de.thm.ii.fbs.utils.v2.exceptions.NotFoundException import org.springframework.data.repository.findByIdOrNull import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.ResponseStatus -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* @RestController class RunnerApiController( @@ -31,6 +28,7 @@ class RunnerApiController( queryRepository.save(query) } + private fun updateAllEntity(query: SqlPlaygroundQuery, result: SqlPlaygroundRunnerResult) { // Do not update the entity if the query had an error, as the checker will not return db information if there was an error if (result.error) return diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt index 2c1103beb..a92a8ce4d 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt @@ -3,5 +3,6 @@ package de.thm.ii.fbs.model.v2.checker import com.fasterxml.jackson.annotation.JsonValue enum class RunnerType(@JsonValue val label: String) { - SQL_PLAYGROUND("sql-playground") + SQL_PLAYGROUND("sql-playground"), + SHARE_PLAYGROUND("share") } diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt new file mode 100644 index 000000000..be7da7cea --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt @@ -0,0 +1,12 @@ +package de.thm.ii.fbs.model.v2.checker + +import com.fasterxml.jackson.annotation.JsonProperty + +data class SharePlaygroundArguments( + @JsonProperty("user") + val user: RunnerUser, + @JsonProperty("database") + val database: RunnerDatabase, + @JsonProperty("runner") + val runner: Runner = Runner(RunnerType.SHARE_PLAYGROUND) +) : RunnerArguments() \ No newline at end of file diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt new file mode 100644 index 000000000..4a7260ba2 --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt @@ -0,0 +1,14 @@ +package de.thm.ii.fbs.model.v2.security +import java.time.LocalDateTime +import javax.persistence.Entity +import javax.persistence.Id + +@Entity +class SharePlaygroundToken( + @Id + val token: String, + val userId: Int, + val dbId: Int, + val expiryTime: LocalDateTime, + val uri: String +) \ No newline at end of file diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt index 1a4b13755..939139456 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt @@ -1,20 +1,22 @@ package de.thm.ii.fbs.services.v2.checker -import de.thm.ii.fbs.model.v2.checker.RunnerDatabase -import de.thm.ii.fbs.model.v2.checker.RunnerUser -import de.thm.ii.fbs.model.v2.checker.SqlPlaygroundRunnerArguments -import de.thm.ii.fbs.model.v2.checker.SqlPlaygroundRunnerDeleteArguments +import de.thm.ii.fbs.model.v2.checker.* import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundDatabase import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundQuery +import de.thm.ii.fbs.model.v2.security.SharePlaygroundToken +import de.thm.ii.fbs.services.v2.persistence.SharePlaygroundTokenRepository import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service +import java.time.LocalDateTime +import java.util.* @Service class SqlPlaygroundCheckerService( @Value("\${services.masterRunner.insecure}") insecure: Boolean, @Value("\${services.masterRunner.url}") - private val masterRunnerURL: String + private val masterRunnerURL: String, + private val sharePlaygroundTokenRepository: SharePlaygroundTokenRepository, ) : RemoteCheckerV2Service(insecure, masterRunnerURL) { fun submit(query: SqlPlaygroundQuery) { @@ -31,6 +33,18 @@ class SqlPlaygroundCheckerService( ) } + fun createSharePlayground(db: SqlPlaygroundDatabase): String { + val token = UUID.randomUUID().toString() + val expiryTime = LocalDateTime.now() + val uri = this.sendToRunner( + SharePlaygroundArguments( + RunnerUser(db.owner.id!!, db.owner.username), + RunnerDatabase(db.id!!, db.name) + )) + sharePlaygroundTokenRepository.save(SharePlaygroundToken(token, db.owner.id!!, db.id!!, expiryTime, uri.toString())) + return uri.toString() + } + fun deleteDatabase(database: SqlPlaygroundDatabase, userId: Int, username: String) { this.sendToRunner( SqlPlaygroundRunnerDeleteArguments( diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt new file mode 100644 index 000000000..97606397a --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt @@ -0,0 +1,9 @@ +package de.thm.ii.fbs.services.v2.persistence + +import de.thm.ii.fbs.model.v2.security.SharePlaygroundToken +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface SharePlaygroundTokenRepository : JpaRepository { + fun findAllByExpiryTimeBefore(now: LocalDateTime?): List +} \ No newline at end of file diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt new file mode 100644 index 000000000..c10aa3673 --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt @@ -0,0 +1,25 @@ +import de.thm.ii.fbs.model.v2.security.SharePlaygroundToken +import de.thm.ii.fbs.services.v2.persistence.SharePlaygroundTokenRepository +import de.thm.ii.fbs.services.v2.persistence.SqlPlaygroundDatabaseRepository +import de.thm.ii.fbs.utils.v2.exceptions.NotFoundException +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class SharePlaygroundCleanupService( + private val sharePlaygroundTokenRepository: SharePlaygroundTokenRepository, + private val databaseRepository: SqlPlaygroundDatabaseRepository +) { + @Scheduled(fixedDelayString = "PT24H") // Runs every 24h + fun cleanupExpiredDumps() { + val now = LocalDateTime.now() + val expiredTokens: List = sharePlaygroundTokenRepository.findAllByExpiryTimeBefore(now) + expiredTokens.forEach { token -> + val db = databaseRepository.findByOwner_IdAndIdAndDeleted(token.userId, token.dbId, false) + ?: throw NotFoundException() + //sqlPlaygroundCheckerService.deleteDatabase(db, db.id!!, db.name) WIP + sharePlaygroundTokenRepository.delete(token) + } + } +} diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala index 1445d096e..d1eebd7c6 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala @@ -2,7 +2,7 @@ package de.thm.ii.fbs.services.runner import de.thm.ii.fbs.services.ExtendedResultsService import de.thm.ii.fbs.services.db.{DBOperationsService, PsqlOperationsService} -import de.thm.ii.fbs.types.{OutputJsonStructure, SqlPlaygroundRunArgs} +import de.thm.ii.fbs.types.{OutputJsonStructure, SharePlaygroundArgs, SqlPlaygroundRunArgs} import de.thm.ii.fbs.util.{DBTypes, DatabaseInformationService, PlaygroundDBConnections, SqlPlaygroundMode} import io.vertx.core.json.JsonObject import io.vertx.scala.ext.sql.ResultSet @@ -21,6 +21,11 @@ object SQLPlaygroundService { OutputJsonStructure("triggers", Option("manipulation")) ) +// change this function with dump function + def test(): String = { + s"Test is working" + } + def isPlaygroundResult(res: JsonObject): Boolean = res.getString("resultType", "").equals(PLAYGROUND_RESULT_TYPE) /** diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala new file mode 100644 index 000000000..c3e114c51 --- /dev/null +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala @@ -0,0 +1,7 @@ +package de.thm.ii.fbs.types + +import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonProperty} + +@JsonIgnoreProperties(ignoreUnknown = true) // Ignore runner Type +case class SharePlaygroundArgs(@JsonProperty("user") user: User, + @JsonProperty("database") database: Database) \ No newline at end of file diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala index 5b6f5e020..1ba0fbdfe 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala @@ -2,7 +2,7 @@ package de.thm.ii.fbs.verticles import de.thm.ii.fbs.services.runner.SQLPlaygroundService import de.thm.ii.fbs.verticles.HttpVerticle.SEND_COMPLETION -import de.thm.ii.fbs.verticles.runner.{BashRunnerVerticle, SqlCheckerVerticle, SqlPlaygroundVerticle, SqlRunnerVerticle} +import de.thm.ii.fbs.verticles.runner.{BashRunnerVerticle, SharePlaygroundVerticle, SqlCheckerVerticle, SqlPlaygroundVerticle, SqlRunnerVerticle} import io.vertx.lang.scala.json.JsonObject import io.vertx.lang.scala.{ScalaLogger, ScalaVerticle} import io.vertx.scala.core.eventbus.Message @@ -94,6 +94,9 @@ class HttpVerticle extends ScalaVerticle { case "sql-playground" => vertx.eventBus().send(SqlPlaygroundVerticle.RUN_ADDRESS, body) ctx.response().setStatusCode(202).end() + case "share" => + vertx.eventBus().send(SharePlaygroundVerticle.RUN_ADDRESS, body) + ctx.response().setStatusCode(202).end() case _ => ctx.response().setStatusCode(404).end("Invalid Runner Type") } } diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala new file mode 100644 index 000000000..ac9cb32cf --- /dev/null +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala @@ -0,0 +1,43 @@ +package de.thm.ii.fbs.verticles.runner + +import de.thm.ii.fbs.services.runner.SQLPlaygroundService +import de.thm.ii.fbs.types._ +import de.thm.ii.fbs.verticles.runner.SharePlaygroundVerticle.RUN_ADDRESS +import io.vertx.lang.scala.{ScalaLogger, ScalaVerticle} +import io.vertx.scala.core.eventbus.Message +import io.vertx.core.json.JsonObject + +import scala.concurrent.Future + +class SharePlaygroundVerticle extends ScalaVerticle { + private val logger = ScalaLogger.getLogger(getClass.getName) + + override def startFuture(): Future[_] = { + vertx.eventBus().consumer(RUN_ADDRESS, handleTest).completionFuture() + } + + //placeholder function + private def handleTest(msg: Message[JsonObject]): Future[Unit] = Future { + val argsJson = msg.body() + + val userJson = argsJson.getJsonObject("user") + val databaseJson = argsJson.getJsonObject("database") + + val userId = userJson.getInteger("id") + val username = userJson.getString("username") + val databaseName = databaseJson.getString("name") + val databaseid = databaseJson.getInteger("id") + + val user = new User(userId, username) + val database = Database(databaseid, databaseName) + + val args = SharePlaygroundArgs(user, database) + + val response = SQLPlaygroundService.test() + msg.reply(response) + } +} + +object SharePlaygroundVerticle { + val RUN_ADDRESS = "de.thm.ii.fbs.runner.share" +} \ No newline at end of file From 42494434655deb46bd2114f12618c619bd23cf08 Mon Sep 17 00:00:00 2001 From: Khaled Trabelsi <131378411+trKhaled@users.noreply.github.com> Date: Wed, 22 Nov 2023 07:21:23 +0100 Subject: [PATCH 2/5] 1482 web fronted for sql playground sharing (#1523) * added button and its functionality; Service still needs to be updated * changed uri display to dialog * changed service function * changed service path * refactoring --------- Co-authored-by: Jonas Kuche --- modules/fbs-core/web/src/app/app.module.ts | 2 + ...hare-playground-link-dialog.component.html | 23 +++++++++++ ...hare-playground-link-dialog.component.scss | 25 ++++++++++++ .../share-playground-link-dialog.component.ts | 38 +++++++++++++++++++ .../db-control-db-overview.component.html | 9 +++++ .../db-control-db-overview.component.scss | 6 +++ .../db-control-db-overview.component.ts | 18 +++++++++ .../sql-playground.component.scss | 6 +-- .../src/app/service/sql-playground.service.ts | 14 ++++++- 9 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.html create mode 100644 modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.scss create mode 100644 modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.ts diff --git a/modules/fbs-core/web/src/app/app.module.ts b/modules/fbs-core/web/src/app/app.module.ts index 0da179435..782935490 100644 --- a/modules/fbs-core/web/src/app/app.module.ts +++ b/modules/fbs-core/web/src/app/app.module.ts @@ -108,6 +108,7 @@ import { HighlightedInputComponent } from "./page-components/sql-playground/sql- import "mathlive"; import "@cortex-js/compute-engine"; import { MathInputComponent } from "./tool-components/math-input/math-input.component"; +import { SharePlaygroundLinkDialogComponent } from "./dialogs/share-playground-link-dialog/share-playground-link-dialog.component"; import { FbsModellingComponent } from "./page-components/fbs-modelling/fbs-modelling.component"; import { I18NextModule } from "angular-i18next"; import { I18N_PROVIDERS } from "./util/i18n"; @@ -213,6 +214,7 @@ export const httpInterceptorProviders = [ ExportTasksDialogComponent, HighlightedInputComponent, MathInputComponent, + SharePlaygroundLinkDialogComponent, FbsModellingComponent, LanguageMenuComponent, ], diff --git a/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.html b/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.html new file mode 100644 index 000000000..2ed5b55eb --- /dev/null +++ b/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.html @@ -0,0 +1,23 @@ +
+
Temporäre Datenbank URI
+ + + +

{{ data.message }}

+
+
+ {{ data.uri }} + +
+
+
+ + + +
diff --git a/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.scss b/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.scss new file mode 100644 index 000000000..ae5c3af1c --- /dev/null +++ b/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.scss @@ -0,0 +1,25 @@ +.container { + height: 100%; +} + +.actions { + display: flex; + justify-content: flex-end; + button { + margin-left: 16px; + } +} + +.content { + display: block; + margin-top: 10px; +} + +.uri{ + margin-top: 30px; + button { + margin-left: 20px; + } +} + + diff --git a/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.ts b/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.ts new file mode 100644 index 000000000..0cf84e6e9 --- /dev/null +++ b/modules/fbs-core/web/src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component, Inject } from "@angular/core"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { MatSnackBar } from "@angular/material/snack-bar"; + +@Component({ + selector: "app-db-uri-link-dialog", + templateUrl: "./share-playground-link-dialog.component.html", + styleUrls: ["./share-playground-link-dialog.component.scss"], +}) +export class SharePlaygroundLinkDialogComponent { + constructor( + @Inject(MAT_DIALOG_DATA) + public data: { + message: string; + uri: string; + }, + public dialogRef: MatDialogRef, + private snackbar: MatSnackBar + ) {} + + copyURI() { + navigator.clipboard.writeText(this.data.uri).then( + () => { + this.snackbar.open("URI erfolgreich kopiert!", "Ok", { + duration: 3000, + }); + }, + (error) => { + console.error("URI konnte nicht kopiert werden: ", error); + this.snackbar.dismiss(); + } + ); + } + + closeDialog() { + this.dialogRef.close({ success: false }); + } +} diff --git a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.html b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.html index c38245213..0b68c3a0a 100644 --- a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.html +++ b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.html @@ -57,5 +57,14 @@ | i18nextEager }} + + diff --git a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.scss b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.scss index 122cf39bf..c667414b6 100644 --- a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.scss +++ b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.scss @@ -31,6 +31,12 @@ mat-form-field { grid-column: 1 / 3; } +.url-btn { + color: white; + background-color: rgba(25, 105, 243, 0.792); + grid-column: 1 / 3; +} + button { height: 30px; display: flex; diff --git a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts index 6be8f533d..451876778 100644 --- a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts +++ b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts @@ -8,6 +8,7 @@ import { JWTToken } from "src/app/model/JWTToken"; import { TextConfirmDialogComponent } from "../../../../dialogs/text-confirm-dialog/text-confirm-dialog.component"; import { NewDbDialogComponent } from "../../../../dialogs/new-db-dialog/new-db-dialog.component"; import { MatDialog } from "@angular/material/dialog"; +import { SharePlaygroundLinkDialogComponent } from "src/app/dialogs/share-playground-link-dialog/share-playground-link-dialog.component"; @Component({ selector: "app-db-control-db-overview", @@ -31,6 +32,7 @@ export class DbControlDbOverviewComponent implements OnInit { selectedDb: number = 0; token: JWTToken = this.authService.getToken(); pending: boolean = false; + dbURI: string = ""; ngOnInit(): void { this.sqlPlaygroundService.getDatabases(this.token.id).subscribe( @@ -180,6 +182,22 @@ export class DbControlDbOverviewComponent implements OnInit { return dialogRef.afterClosed(); } + getTempURI() { + const selectedDb = this.dbs.find((db) => db.id == this.selectedDb); + this.sqlPlaygroundService + .getSharePlaygroundURI(this.token.id, selectedDb.id) + .subscribe((uri) => (this.dbURI = uri)); + this.dialog.open(SharePlaygroundLinkDialogComponent, { + height: "auto", + width: "50%", + autoFocus: false, + data: { + message: `Der URI-Link zu deiner Datenbank \" ${selectedDb.name} \" ist nur für 24 Stunden verfügbar!\n`, + uri: this.dbURI, + }, + }); + } + addDb() { this.dialog .open(NewDbDialogComponent, { diff --git a/modules/fbs-core/web/src/app/page-components/sql-playground/sql-playground.component.scss b/modules/fbs-core/web/src/app/page-components/sql-playground/sql-playground.component.scss index 0cbfc8afb..5fbcd96e9 100644 --- a/modules/fbs-core/web/src/app/page-components/sql-playground/sql-playground.component.scss +++ b/modules/fbs-core/web/src/app/page-components/sql-playground/sql-playground.component.scss @@ -31,16 +31,16 @@ .db-grid { width: 100%; grid-row-start: 1; - grid-row-end: 4; + grid-row-end: 5; grid-column-start: 8; grid-column-end: 11; padding: none; + margin-bottom: 35px; } .db-schema-grid { width: 100%; - margin-top: 20px; - grid-row-start: 4; + grid-row-start: 5; grid-row-end: 9; grid-column-start: 8; grid-column-end: 11; diff --git a/modules/fbs-core/web/src/app/service/sql-playground.service.ts b/modules/fbs-core/web/src/app/service/sql-playground.service.ts index 380518107..d1e39a1e2 100644 --- a/modules/fbs-core/web/src/app/service/sql-playground.service.ts +++ b/modules/fbs-core/web/src/app/service/sql-playground.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable } from "rxjs"; +import { Observable, of } from "rxjs"; import { Constraint } from "../model/sql_playground/Constraint"; import { Database } from "../model/sql_playground/Database"; import { Routine } from "../model/sql_playground/Routine"; @@ -9,6 +9,7 @@ import { SQLResponse } from "../model/sql_playground/SQLResponse"; import { Table } from "../model/sql_playground/Table"; import { Trigger } from "../model/sql_playground/Trigger"; import { View } from "../model/sql_playground/View"; +import { map } from "rxjs/operators"; @Injectable({ providedIn: "root", @@ -79,6 +80,17 @@ export class SqlPlaygroundService { ); } + /** + * Get Database Temp URI + * @return the temporary database URI + */ + getSharePlaygroundURI(uid: number, dbId: number): Observable { + return this.http.post( + `/api/v2/playground/${uid}/databases/${dbId}/dump`, + {} + ); + } + /** * get the result of a query * @param uid User id From b45393d8322fa39d828a186cd845b70c707f5f79 Mon Sep 17 00:00:00 2001 From: Youssef Mellouli <49683272+ymll58@users.noreply.github.com> Date: Wed, 22 Nov 2023 07:23:37 +0100 Subject: [PATCH 3/5] 1481 sql runner support for playground sharing (#1522) * copy the database and create a new user * copy database to another instance --------- Co-authored-by: Jonas Kuche --- .../checker/src/main/resources/config.json | 7 ++- .../fbs/services/db/DBOperationsService.scala | 6 ++- .../services/db/PsqlOperationsService.scala | 37 +++++++++++++ .../runner/SQLPlaygroundService.scala | 10 +++- .../services/runner/SQLRunnerService.scala | 44 ++++++++++++++++ .../runner/SqlPlaygroundVerticle.scala | 52 +++++++++++++++---- 6 files changed, 141 insertions(+), 15 deletions(-) diff --git a/modules/fbs-runner/checker/src/main/resources/config.json b/modules/fbs-runner/checker/src/main/resources/config.json index 6058fab43..d8bd6b00a 100644 --- a/modules/fbs-runner/checker/src/main/resources/config.json +++ b/modules/fbs-runner/checker/src/main/resources/config.json @@ -21,5 +21,10 @@ "SQL_PLAYGROUND_PSQL_SERVER_PASSWORD": "SqfyBWhiFGr7FK60cVR2rel", "SQL_PLAYGROUND_PSQL_SERVER_URL": "jdbc:postgresql://localhost:5432?allowMultiQueries=true", "SQL_QUERY_TIMEOUT_S": 10, - "SQL_MAX_IDLE_TIME": 10 + "SQL_MAX_IDLE_TIME": 10, + "PSQL_SHARING_SERVER_HOST": "psql-sharing", + "PSQL_SHARING_SERVER_PORT": "5444", + "PSQL_SHARING_SERVER_URL": "jdbc:postgresql://psql-sharing:5444", + "PSQL_SHARING_SERVER_USERNAME": "postgres", + "PSQL_SHARING_SERVER_PASSWORD": "R$7pWqY@K5zE3Xt&g9L1MfD" } diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala index cb0bf7b7a..ed549fb11 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala @@ -18,7 +18,11 @@ abstract case class DBOperationsService(dbName: String, username: String, queryT def initDB(client: JDBCClient, query: String): Future[ResultSet] = { queryFutureWithTimeout(client, query) } - +/** + def copyDatabase(targetClient: JDBCClient, targetDBName: String): Future[ResultSet] +*/ + def createUserWithAccess(targetClient: JDBCClient, targetDBName: String, targetHost: String): Future[String] + def copyDatabaseToAnotherInstance(sourceHost: String, sourceDBName: String, targetHost: String, targetDBName: String): Future[Unit] def createUserWithWriteAccess(client: JDBCClient, skipUserCreation: Boolean = false): Future[String] def createUserIfNotExist(client: SQLConnection, password: String): Future[ResultSet] diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala index 8274980b9..ec3564f10 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala @@ -4,9 +4,11 @@ import de.thm.ii.fbs.types.PsqlPrivileges import io.vertx.lang.scala.json.JsonArray import io.vertx.scala.ext.jdbc.JDBCClient import io.vertx.scala.ext.sql.{ResultSet, SQLConnection} +import scala.sys.process._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future +import scala.sys.process.Process import scala.util.{Failure, Success, Try} class PsqlOperationsService(override val dbName: String, override val username: String, override val queryTimeout: Int) @@ -154,6 +156,41 @@ class PsqlOperationsService(override val dbName: String, override val username: client.queryFuture(s"$tables $constrains $views $routines $triggers") } +/** + override def copyDatabase(targetClient: JDBCClient, targetDBName: String): Future[ResultSet] = { + val copyDBQuery = s"CREATE DATABASE $targetDBName WITH TEMPLATE $dbName" + targetClient.queryFuture(copyDBQuery) + } +*/ +def copyDatabaseToAnotherInstance(sourceHost: String, sourceDBName: String, targetHost: String, targetDBName: String): Future[Unit] = Future { + /**Command to dump the database from the source + * missing case where sourcehost has no password*/ + val dumpCommand = s"pg_dump -h $sourceHost -U $username -F c -b -v -f /tmp/$sourceDBName.dump $sourceDBName" + val dumpExitCode = Process(Seq("bash", "-c", dumpCommand), None, "PGPASSWORD" -> "your_source_db_password").! + + if (dumpExitCode != 0) { + throw new Exception(s"Failed to dump the database from $sourceHost") + } + + // Command to restore the dump to the target + val restoreCommand = s"pg_restore -h $targetHost -U $username -d $targetDBName -v /tmp/$sourceDBName.dump" + val restoreExitCode = Process(Seq("bash", "-c", restoreCommand), None, "PGPASSWORD" -> "your_target_db_password").! + + if (restoreExitCode != 0) { + throw new Exception(s"Failed to restore the database to $targetHost") + } +} + + override def createUserWithAccess(targetClient: JDBCClient, targetDBName: String, targetHost: String): Future[String] = { + val password = generateUserPassword() + val userCreateQuery = s"CREATE USER $username WITH PASSWORD '$password';" + val permissionsQuery = s"GRANT ALL PRIVILEGES ON DATABASE $targetDBName TO $username;" + + for { + _ <- targetClient.queryFuture(userCreateQuery) + _ <- targetClient.queryFuture(permissionsQuery) + } yield s"postgresql://$username:$password@$targetHost/$targetDBName" + override def queryFutureWithTimeout(client: JDBCClient, sql: String): Future[ResultSet] = { client.getConnectionFuture().flatMap(con => { con.queryFuture(s"SET statement_timeout = ${queryTimeout*1000};").flatMap(_ => { diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala index d1eebd7c6..6729762d7 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala @@ -6,7 +6,7 @@ import de.thm.ii.fbs.types.{OutputJsonStructure, SharePlaygroundArgs, SqlPlaygro import de.thm.ii.fbs.util.{DBTypes, DatabaseInformationService, PlaygroundDBConnections, SqlPlaygroundMode} import io.vertx.core.json.JsonObject import io.vertx.scala.ext.sql.ResultSet - +import io.vertx.scala.ext.jdbc.JDBCClient import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.util.{Failure, Success} @@ -70,12 +70,18 @@ class SQLPlaygroundService(val sqlRunArgs: SqlPlaygroundRunArgs, val con: Playgr private def initDBOperations(): DBOperationsService = { // Currently only PostgresSql is Supported if (!isPsql) throw new Error("Invalid DBType. Currently only Psql is Supported") - val dbName = s"playground_db_${sqlRunArgs.database.id.toString}" val username = s"playground_user_${sqlRunArgs.user.id.toString}" new PsqlOperationsService(dbName, username, queryTimeout) } + def copyDBAndCreateUser(sourceHost: String, targetHost: String, targetClient: JDBCClient): Future[String] = { + val dbOperations = initDBOperations() + for { + _ <- dbOperations.copyDatabaseToAnotherInstance(sourceHost, dbOperations.dbName, targetHost, dbOperations.dbName) + uri <- dbOperations.createUserWithAccess(targetClient, dbOperations.dbName, targetHost) + } yield uri + } def executeStatement(): Future[(ResultSet, ResultSet)] = { val dbOperations = initDBOperations() val deleteDatabase = SqlPlaygroundMode.shouldDeleteDatabase(sqlRunArgs.mode) diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala index 48496bd8c..93f1ce086 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala @@ -9,7 +9,9 @@ import de.thm.ii.fbs.util.Secrets.getSHAStringFromNow import de.thm.ii.fbs.util.{DBConnections, DBTypes, RunnerException} import io.vertx.core.json.DecodeException import io.vertx.lang.scala.json.JsonObject +import io.vertx.scala.core.Vertx.vertx import io.vertx.scala.ext.sql.ResultSet +import io.vertx.scala.ext.jdbc.JDBCClient import java.sql.SQLException import scala.concurrent.ExecutionContext.Implicits.global @@ -107,6 +109,8 @@ class SQLRunnerService(val sqlRunArgs: SqlRunArgs, val solutionCon: DBConnection private val configDbExt = s"${getSHAStringFromNow()}_c" private val submissionDbExt = s"${getSHAStringFromNow()}_s" + + private def isVariable(taskQuery: TaskQuery): Boolean = taskQuery.order != null && taskQuery.order.equalsIgnoreCase("variable") @@ -137,6 +141,31 @@ class SQLRunnerService(val sqlRunArgs: SqlRunArgs, val solutionCon: DBConnection dbOperation } + /** + def copyPlaygroundDBAndCreateUser(targetDBHost: String, targetDBPort: Int, dbUser: String, dbPassword: String, originalDBName: String, targetDBName: String): Future[String] = { + // Create a client for the target database + val targetJDBCClient = JDBCClient.createNonShared(vertx, new JsonObject() + .put("url", s"jdbc:postgresql://$targetDBHost:$targetDBPort/$targetDBName") + .put("user", dbUser) + .put("password", dbPassword) + ) + + // Initialize a new instance of PsqlOperationsService with the original database name, user, and password + val targetDBOperations = new PsqlOperationsService(originalDBName, dbUser, queryTimout) + + // First, copy the database to the new instance + val copyDBFuture = targetDBOperations.copyDatabase(targetJDBCClient, targetDBName) + + // Then, create a new user with access to the copied database + val createUserFuture = copyDBFuture.flatMap { _ => + targetDBOperations.createUserWithAccess(targetJDBCClient, targetDBName) + } + // Return the PostgreSQL URI + createUserFuture.map { newUserAndPassword => + s"jdbc:postgresql://$targetDBHost:$targetDBPort/$targetDBName?user=$newUserAndPassword._1&password=$newUserAndPassword._2" + } + } +*/ private def buildName(nameExtension: String): String = s"${sqlRunArgs.submissionId}_${sqlRunArgs.runnerId}_$nameExtension" /** @@ -275,4 +304,19 @@ class SQLRunnerService(val sqlRunArgs: SqlRunArgs, val solutionCon: DBConnection (msg, success, correctResults) } + + /** def copyPlaygroundDBAndCreateUser(targetDBHost: String, targetDBPort: Int): Future[String] = { + val targetDBName = s"${dbName}_copy" + val targetJDBCClient = JDBCClient.createNonShared(vertx, new JsonObject() + .put("url", s"jdbc:postgresql://$targetDBHost:$targetDBPort/") + .put("user", username) + .put("password", password) + ) + + for { + _ <- copyDatabase(targetJDBCClient, targetDBName) + uri <- createUserWithAccess(targetJDBCClient, targetDBName) + } yield uri + } +**/ } diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala index 5ddc84602..4b7f7bd85 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala @@ -1,6 +1,7 @@ package de.thm.ii.fbs.verticles.runner -import de.thm.ii.fbs.services.runner.SQLPlaygroundService +import de.thm.ii.fbs.services.db.PsqlOperationsService +import de.thm.ii.fbs.services.runner.{SQLPlaygroundService} import de.thm.ii.fbs.types._ import de.thm.ii.fbs.util.DBTypes.PSQL_CONFIG_KEY import de.thm.ii.fbs.util.{Metrics, PlaygroundDBConnections} @@ -18,18 +19,18 @@ import scala.concurrent.Future import scala.util.{Failure, Success} /** - * Object that stores all static vars for the SqlPlaygroundVerticle - */ + * Object that stores all static vars for the SqlPlaygroundVerticle + */ object SqlPlaygroundVerticle { /** Event Bus Address to start an runner */ val RUN_ADDRESS = "de.thm.ii.fbs.runner.sqlPlayground" } /** - * Verticle that starts the SqlPlayground - * - * @author Max Stephan - */ + * Verticle that starts the SqlPlayground + * + * @author Max Stephan + */ class SqlPlaygroundVerticle extends ScalaVerticle { private val logger = ScalaLogger.getLogger(this.getClass.getName) private var sqlPools = Map[String, SqlPoolWithConfig]() @@ -39,10 +40,10 @@ class SqlPlaygroundVerticle extends ScalaVerticle { private val errorCounter = meter.counterBuilder("errorCount").setDescription("Error Count").build() /** - * start SqlRunnerVerticle - * - * @return vertx Future - */ + * start SqlRunnerVerticle + * + * @return vertx Future + */ override def startFuture(): Future[_] = { val psqlDataSource = s"${PSQL_CONFIG_KEY}_playground" val psqlConfig = new JsonObject() @@ -59,6 +60,30 @@ class SqlPlaygroundVerticle extends ScalaVerticle { vertx.eventBus().consumer(RUN_ADDRESS, startSqlPlayground).completionFuture() } + private def copyDatabaseAndCreateUser(runArgs: SqlPlaygroundRunArgs): Future[String] = { + // Extract sqlRunArgs from the message + + + // Get the PlaygroundDBConnections + val con = getConnection(runArgs).getOrElse(throw new Exception("Failed to get database connection")) + + // Get the query timeout from config + val queryTimeout = config.getInteger("SQL_QUERY_TIMEOUT_S", 10) + + // Create an instance of SQLPlaygroundService + val sqlPlaygroundService = new SQLPlaygroundService(runArgs, con, queryTimeout) + + // Extract source and target host + val sourceHost = config.getString("PSQL_SERVER_URL", "jdbc:postgresql://localhost:5432").split("//")(1).split("/")(0) + val targetHost = config.getString("PSQL_SHARING_SERVER_URL", "jdbc:postgresql://psql-sharing:5432").split("//")(1).split("/")(0) + + // Get the JDBC client for the target database + val targetClient = sqlPools.getOrElse(PSQL_CONFIG_KEY, throw new Exception("PSQL config not found")).pool + + // Call the method to copy database and create user + sqlPlaygroundService.copyDBAndCreateUser(sourceHost, targetHost, targetClient) + } + private def startSqlPlayground(msg: Message[JsonObject]): Future[Unit] = Future { val runArgs = msg.body().mapTo(classOf[SqlPlaygroundRunArgs]) @@ -83,6 +108,11 @@ class SqlPlaygroundVerticle extends ScalaVerticle { case Failure(_) => end(true) } + // Call the method to copy database and create user + copyDatabaseAndCreateUser(runArgs).onComplete { + case Success(uri) => logger.info(s"Database copied, new URI: $uri") + case Failure(ex) => logger.error("Error in copying database and creating user", ex) + } } else { end(false) } From 8723792556f9145d510193a70334416c79e0272e Mon Sep 17 00:00:00 2001 From: Jonas Kuche Date: Thu, 23 Nov 2023 09:05:26 +0100 Subject: [PATCH 4/5] feat(playground): add database sharing --- modules/fbs-core/api/build.gradle | 1 + .../fbs/controller/v2/PlaygroundController.kt | 8 +- .../thm/ii/fbs/model/v2/checker/RunnerType.kt | 2 +- ...ents.kt => SqlPlaygroundShareArguments.kt} | 10 +- .../SqlPlaygroundShareDeleteArguments.kt | 14 ++ .../model/v2/playground/SqlPlaygroundShare.kt | 16 ++ .../api/SqlPlaygroundShareResponse.kt | 8 + .../model/v2/security/SharePlaygroundToken.kt | 14 -- .../v2/checker/SqlPlaygroundCheckerService.kt | 40 +++-- .../thm/ii/fbs/services/v2/misc/IdService.kt | 21 +++ .../v2/misc/SharePlaygroundCleanupService.kt | 23 +++ .../SharePlaygroundTokenRepository.kt | 9 - .../SqlSharePlaygroundShareRepository.kt | 9 + .../SharePlaygroundCleanupService.kt | 25 --- .../api/src/main/resources/application.yml | 7 + .../migrations/19_playground_share_token.sql | 16 ++ .../app/model/sql_playground/SQLResponse.ts | 4 + .../db-control-db-overview.component.ts | 22 +-- .../src/app/service/sql-playground.service.ts | 9 +- modules/fbs-runner/checker/Dockerfile | 2 +- .../scala/de/thm/ii/fbs/MainVerticle.scala | 10 +- .../fbs/services/db/DBOperationsService.scala | 6 +- .../db/PsqlNativeOperationsService.scala | 26 +++ .../services/db/PsqlOperationsService.scala | 63 +++---- .../runner/SQLPlaygroundService.scala | 17 +- .../runner/SQLPlaygroundShareService.scala | 5 + .../services/runner/SQLRunnerService.scala | 44 ----- .../ii/fbs/types/SharePlaygroundArgs.scala | 7 - .../ii/fbs/types/SqlPlaygroundShareArgs.scala | 11 ++ .../ii/fbs/util/PlaygroundDBConnections.scala | 9 +- .../thm/ii/fbs/verticles/HttpVerticle.scala | 6 +- .../runner/SharePlaygroundVerticle.scala | 43 ----- .../runner/SqlPlaygroundShareVerticle.scala | 165 ++++++++++++++++++ .../runner/SqlPlaygroundVerticle.scala | 52 ++---- 34 files changed, 440 insertions(+), 284 deletions(-) rename modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/{SharePlaygroundArguments.kt => SqlPlaygroundShareArguments.kt} (51%) create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareDeleteArguments.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/api/SqlPlaygroundShareResponse.kt delete mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt delete mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt create mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SqlSharePlaygroundShareRepository.kt delete mode 100644 modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt create mode 100644 modules/fbs-core/api/src/main/resources/migrations/19_playground_share_token.sql create mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlNativeOperationsService.scala create mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala delete mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala create mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SqlPlaygroundShareArgs.scala delete mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala create mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundShareVerticle.scala diff --git a/modules/fbs-core/api/build.gradle b/modules/fbs-core/api/build.gradle index e9b797fc7..bca24a9ec 100644 --- a/modules/fbs-core/api/build.gradle +++ b/modules/fbs-core/api/build.gradle @@ -78,6 +78,7 @@ dependencies { testImplementation group: 'junit', name: 'junit', version: '4.13.2' implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.23.0' implementation group: 'org.jgrapht', name: 'jgrapht-core', version: '1.5.2' + implementation group: 'org.hashids', name: 'hashids', version: '1.0.3' } jacoco { diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt index 3246cf947..0e8ae31d2 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/PlaygroundController.kt @@ -8,6 +8,7 @@ import de.thm.ii.fbs.model.v2.playground.* import de.thm.ii.fbs.model.v2.playground.api.SqlPlaygroundDatabaseCreation import de.thm.ii.fbs.model.v2.playground.api.SqlPlaygroundQueryCreation import de.thm.ii.fbs.model.v2.playground.api.SqlPlaygroundResult +import de.thm.ii.fbs.model.v2.playground.api.SqlPlaygroundShareResponse import de.thm.ii.fbs.model.v2.security.LegacyToken import de.thm.ii.fbs.services.v2.checker.SqlPlaygroundCheckerService import de.thm.ii.fbs.services.v2.persistence.* @@ -72,11 +73,12 @@ class PlaygroundController( return databaseRepository.save(db) } - @PostMapping("/{dbId}/dump") + @PostMapping("/{dbId}/share") @ResponseBody - fun createSharePlayground(@CurrentToken currentToken: LegacyToken, @PathVariable("dbId") dbId: Int): String { + fun createPlaygroundShare(@CurrentToken currentToken: LegacyToken, @PathVariable("dbId") dbId: Int): SqlPlaygroundShareResponse { val currentActiveDb = databaseRepository.findByOwner_IdAndIdAndDeleted(currentToken.id, dbId, false) ?: throw NotFoundException() - return sqlPlaygroundCheckerService.createSharePlayground(currentActiveDb) + val url = sqlPlaygroundCheckerService.shareDatabase(currentActiveDb) + return SqlPlaygroundShareResponse(url) } @PostMapping("/{dbId}/reset") diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt index a92a8ce4d..af0191b9c 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/RunnerType.kt @@ -4,5 +4,5 @@ import com.fasterxml.jackson.annotation.JsonValue enum class RunnerType(@JsonValue val label: String) { SQL_PLAYGROUND("sql-playground"), - SHARE_PLAYGROUND("share") + SQL_PLAYGROUND_SHARE("sql-playground-share") } diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareArguments.kt similarity index 51% rename from modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt rename to modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareArguments.kt index be7da7cea..7aaf9563a 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SharePlaygroundArguments.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareArguments.kt @@ -2,11 +2,15 @@ package de.thm.ii.fbs.model.v2.checker import com.fasterxml.jackson.annotation.JsonProperty -data class SharePlaygroundArguments( +data class SqlPlaygroundShareArguments( @JsonProperty("user") val user: RunnerUser, @JsonProperty("database") val database: RunnerDatabase, + @JsonProperty("id") + val id: String, + @JsonProperty("password") + val password: String, @JsonProperty("runner") - val runner: Runner = Runner(RunnerType.SHARE_PLAYGROUND) -) : RunnerArguments() \ No newline at end of file + val runner: Runner = Runner(RunnerType.SQL_PLAYGROUND_SHARE) +) : RunnerArguments() diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareDeleteArguments.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareDeleteArguments.kt new file mode 100644 index 000000000..d6ac9517f --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/checker/SqlPlaygroundShareDeleteArguments.kt @@ -0,0 +1,14 @@ +package de.thm.ii.fbs.model.v2.checker + +import com.fasterxml.jackson.annotation.JsonProperty + +data class SqlPlaygroundShareDeleteArguments( + @JsonProperty("database") + val database: RunnerDatabase, + @JsonProperty("id") + val id: String, + @JsonProperty("delete") + val delete: Boolean = true, + @JsonProperty("runner") + val runner: Runner = Runner(RunnerType.SQL_PLAYGROUND_SHARE) +) : RunnerArguments() diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt new file mode 100644 index 000000000..a6effe7da --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt @@ -0,0 +1,16 @@ +package de.thm.ii.fbs.model.v2.playground +import java.time.LocalDateTime +import javax.persistence.* + +@Entity +@Table(name = "sql_playground_share") +class SqlPlaygroundShare( + @OneToOne(cascade = [CascadeType.ALL]) + @PrimaryKeyJoinColumn + val database: SqlPlaygroundDatabase, + @Column(nullable = false) + val creationTime: LocalDateTime, + @Id + @Column(name = "database_id") + val id: Int? = null, +) diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/api/SqlPlaygroundShareResponse.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/api/SqlPlaygroundShareResponse.kt new file mode 100644 index 000000000..f29f0b9d7 --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/api/SqlPlaygroundShareResponse.kt @@ -0,0 +1,8 @@ +package de.thm.ii.fbs.model.v2.playground.api + +import com.fasterxml.jackson.annotation.JsonProperty + +data class SqlPlaygroundShareResponse( + @JsonProperty("url") + var url: String +) diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt deleted file mode 100644 index 4a7260ba2..000000000 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/security/SharePlaygroundToken.kt +++ /dev/null @@ -1,14 +0,0 @@ -package de.thm.ii.fbs.model.v2.security -import java.time.LocalDateTime -import javax.persistence.Entity -import javax.persistence.Id - -@Entity -class SharePlaygroundToken( - @Id - val token: String, - val userId: Int, - val dbId: Int, - val expiryTime: LocalDateTime, - val uri: String -) \ No newline at end of file diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt index 939139456..eb92a6b89 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt @@ -3,8 +3,9 @@ package de.thm.ii.fbs.services.v2.checker import de.thm.ii.fbs.model.v2.checker.* import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundDatabase import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundQuery -import de.thm.ii.fbs.model.v2.security.SharePlaygroundToken -import de.thm.ii.fbs.services.v2.persistence.SharePlaygroundTokenRepository +import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundShare +import de.thm.ii.fbs.services.v2.misc.IdService +import de.thm.ii.fbs.services.v2.persistence.SqlSharePlaygroundShareRepository import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import java.time.LocalDateTime @@ -16,7 +17,12 @@ class SqlPlaygroundCheckerService( insecure: Boolean, @Value("\${services.masterRunner.url}") private val masterRunnerURL: String, - private val sharePlaygroundTokenRepository: SharePlaygroundTokenRepository, + @Value("\${services.sqlPlayground.share.publicHost}") + private val publicShareHost: String, + @Value("\${services.sqlPlayground.share.publicPort}") + private val publicSharePort: Int, + private val sqlSharePlaygroundShareRepository: SqlSharePlaygroundShareRepository, + private val idService: IdService, ) : RemoteCheckerV2Service(insecure, masterRunnerURL) { fun submit(query: SqlPlaygroundQuery) { @@ -33,16 +39,30 @@ class SqlPlaygroundCheckerService( ) } - fun createSharePlayground(db: SqlPlaygroundDatabase): String { + fun shareDatabase(db: SqlPlaygroundDatabase): String { + val id = idService.encode(db.id!!) val token = UUID.randomUUID().toString() - val expiryTime = LocalDateTime.now() - val uri = this.sendToRunner( - SharePlaygroundArguments( + val creationTime = LocalDateTime.now() + this.sendToRunner( + SqlPlaygroundShareArguments( + RunnerUser(db.owner.id!!, db.owner.username), - RunnerDatabase(db.id!!, db.name) + RunnerDatabase(db.id!!, db.name), + id, + token )) - sharePlaygroundTokenRepository.save(SharePlaygroundToken(token, db.owner.id!!, db.id!!, expiryTime, uri.toString())) - return uri.toString() + sqlSharePlaygroundShareRepository.save(SqlPlaygroundShare(db, creationTime, db.id!!)) + return "postgresql://${id}:${token}@${publicShareHost}:${publicSharePort}/${id}" + } + + fun deleteDatabaseShare(share: SqlPlaygroundShare) { + /*val db = share.database + this.sendToRunner( + SqlPlaygroundShareDeleteArguments( + RunnerDatabase(db.id!!, db.name), + idService.encode(share.database.id!!), + )) + sqlSharePlaygroundShareRepository.delete(share)*/ } fun deleteDatabase(database: SqlPlaygroundDatabase, userId: Int, username: String) { diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt new file mode 100644 index 000000000..7a20b62e2 --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt @@ -0,0 +1,21 @@ +package de.thm.ii.fbs.services.v2.misc + +import org.hashids.Hashids +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class IdService( + @Value("\${services.ids.salt}") + salt: String, + @Value("\${services.ids.length}") + length: Int, +) { + private val hashids = Hashids(salt, length) + + fun encode(id: Long): String = + hashids.encode(id) + + fun encode(id: Int): String = + encode(id.toLong()) +} diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt new file mode 100644 index 000000000..222547ff6 --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt @@ -0,0 +1,23 @@ +package de.thm.ii.fbs.services.v2.misc + +import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundShare +import de.thm.ii.fbs.services.v2.checker.SqlPlaygroundCheckerService +import de.thm.ii.fbs.services.v2.persistence.SqlSharePlaygroundShareRepository +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class SharePlaygroundCleanupService( + private val sqlSharePlaygroundShareRepository: SqlSharePlaygroundShareRepository, + private val sqlPlaygroundCheckerService: SqlPlaygroundCheckerService, +) { + @Scheduled(fixedDelayString = "PT1H") // Runs every hour + fun cleanupExpiredShares() { + val now = LocalDateTime.now() + val expiredShares: List = sqlSharePlaygroundShareRepository.findAllByCreationTimeLessThan(now.minusDays(1)) + expiredShares.forEach { share -> + sqlPlaygroundCheckerService.deleteDatabaseShare(share) + } + } +} diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt deleted file mode 100644 index 97606397a..000000000 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SharePlaygroundTokenRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package de.thm.ii.fbs.services.v2.persistence - -import de.thm.ii.fbs.model.v2.security.SharePlaygroundToken -import org.springframework.data.jpa.repository.JpaRepository -import java.time.LocalDateTime - -interface SharePlaygroundTokenRepository : JpaRepository { - fun findAllByExpiryTimeBefore(now: LocalDateTime?): List -} \ No newline at end of file diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SqlSharePlaygroundShareRepository.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SqlSharePlaygroundShareRepository.kt new file mode 100644 index 000000000..ea814a213 --- /dev/null +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/persistence/SqlSharePlaygroundShareRepository.kt @@ -0,0 +1,9 @@ +package de.thm.ii.fbs.services.v2.persistence + +import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundShare +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDateTime + +interface SqlSharePlaygroundShareRepository : JpaRepository { + fun findAllByCreationTimeLessThan(creationTime: LocalDateTime): List +} diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt deleted file mode 100644 index c10aa3673..000000000 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/sharePlaygroundCleanup/SharePlaygroundCleanupService.kt +++ /dev/null @@ -1,25 +0,0 @@ -import de.thm.ii.fbs.model.v2.security.SharePlaygroundToken -import de.thm.ii.fbs.services.v2.persistence.SharePlaygroundTokenRepository -import de.thm.ii.fbs.services.v2.persistence.SqlPlaygroundDatabaseRepository -import de.thm.ii.fbs.utils.v2.exceptions.NotFoundException -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Service -import java.time.LocalDateTime - -@Service -class SharePlaygroundCleanupService( - private val sharePlaygroundTokenRepository: SharePlaygroundTokenRepository, - private val databaseRepository: SqlPlaygroundDatabaseRepository -) { - @Scheduled(fixedDelayString = "PT24H") // Runs every 24h - fun cleanupExpiredDumps() { - val now = LocalDateTime.now() - val expiredTokens: List = sharePlaygroundTokenRepository.findAllByExpiryTimeBefore(now) - expiredTokens.forEach { token -> - val db = databaseRepository.findByOwner_IdAndIdAndDeleted(token.userId, token.dbId, false) - ?: throw NotFoundException() - //sqlPlaygroundCheckerService.deleteDatabase(db, db.id!!, db.name) WIP - sharePlaygroundTokenRepository.delete(token) - } - } -} diff --git a/modules/fbs-core/api/src/main/resources/application.yml b/modules/fbs-core/api/src/main/resources/application.yml index 387614c56..e7c624e76 100644 --- a/modules/fbs-core/api/src/main/resources/application.yml +++ b/modules/fbs-core/api/src/main/resources/application.yml @@ -60,6 +60,13 @@ services: url: ${MASTER_RUNNER_URL:https://localhost:8081} insecure: ${MASTER_RUNNER_TLS_INSECURE:false} selfUrl: ${SELF_URL:https://localhost} + sqlPlayground: + share: + publicHost: ${SQL_PLAYGROUND_SHARE_PUBLIC_HOST:127.0.0.1} + publicPort: ${SQL_PLAYGROUND_SHARE_PUBLIC_PORT:8432} + ids: + salt: ${ID_SALT:feedbacksystem_id_salt} + length: ${ID_LENGTH:8} spring: main: allow-bean-definition-overriding: true diff --git a/modules/fbs-core/api/src/main/resources/migrations/19_playground_share_token.sql b/modules/fbs-core/api/src/main/resources/migrations/19_playground_share_token.sql new file mode 100644 index 000000000..f387ead00 --- /dev/null +++ b/modules/fbs-core/api/src/main/resources/migrations/19_playground_share_token.sql @@ -0,0 +1,16 @@ +BEGIN; + +CREATE TABLE sql_playground_share +( + database_id INT NOT NULL, + creation_time TIMESTAMP NOT NULL, + CONSTRAINT sql_playground_share_pk + PRIMARY KEY (database_id), + constraint sql_playground_share_sql_playground_database_id_fk + FOREIGN KEY (database_id) references sql_playground_database (id) + ON UPDATE CASCADE +); + + +INSERT INTO migration (number) VALUES (19); +COMMIT; diff --git a/modules/fbs-core/web/src/app/model/sql_playground/SQLResponse.ts b/modules/fbs-core/web/src/app/model/sql_playground/SQLResponse.ts index 29ee499d9..1deb2da58 100644 --- a/modules/fbs-core/web/src/app/model/sql_playground/SQLResponse.ts +++ b/modules/fbs-core/web/src/app/model/sql_playground/SQLResponse.ts @@ -8,3 +8,7 @@ interface ResponseTable { head: string[]; rows: string[]; } + +export interface SQLPlaygroundShare { + url: string; +} diff --git a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts index 451876778..a2ca67989 100644 --- a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts +++ b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts @@ -32,7 +32,6 @@ export class DbControlDbOverviewComponent implements OnInit { selectedDb: number = 0; token: JWTToken = this.authService.getToken(); pending: boolean = false; - dbURI: string = ""; ngOnInit(): void { this.sqlPlaygroundService.getDatabases(this.token.id).subscribe( @@ -186,16 +185,17 @@ export class DbControlDbOverviewComponent implements OnInit { const selectedDb = this.dbs.find((db) => db.id == this.selectedDb); this.sqlPlaygroundService .getSharePlaygroundURI(this.token.id, selectedDb.id) - .subscribe((uri) => (this.dbURI = uri)); - this.dialog.open(SharePlaygroundLinkDialogComponent, { - height: "auto", - width: "50%", - autoFocus: false, - data: { - message: `Der URI-Link zu deiner Datenbank \" ${selectedDb.name} \" ist nur für 24 Stunden verfügbar!\n`, - uri: this.dbURI, - }, - }); + .subscribe((share) => { + this.dialog.open(SharePlaygroundLinkDialogComponent, { + height: "auto", + width: "50%", + autoFocus: false, + data: { + message: `Der URI-Link zu deiner Datenbank \" ${selectedDb.name} \" ist nur für 24 Stunden verfügbar!\n`, + uri: share.url, + }, + }); + }) } addDb() { diff --git a/modules/fbs-core/web/src/app/service/sql-playground.service.ts b/modules/fbs-core/web/src/app/service/sql-playground.service.ts index d1e39a1e2..ceb278d95 100644 --- a/modules/fbs-core/web/src/app/service/sql-playground.service.ts +++ b/modules/fbs-core/web/src/app/service/sql-playground.service.ts @@ -1,15 +1,14 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { Observable, of } from "rxjs"; +import { Observable } from "rxjs"; import { Constraint } from "../model/sql_playground/Constraint"; import { Database } from "../model/sql_playground/Database"; import { Routine } from "../model/sql_playground/Routine"; import { SQLExecuteResponse } from "../model/sql_playground/SQLExecuteResponse"; -import { SQLResponse } from "../model/sql_playground/SQLResponse"; +import {SQLPlaygroundShare, SQLResponse} from "../model/sql_playground/SQLResponse"; import { Table } from "../model/sql_playground/Table"; import { Trigger } from "../model/sql_playground/Trigger"; import { View } from "../model/sql_playground/View"; -import { map } from "rxjs/operators"; @Injectable({ providedIn: "root", @@ -84,9 +83,9 @@ export class SqlPlaygroundService { * Get Database Temp URI * @return the temporary database URI */ - getSharePlaygroundURI(uid: number, dbId: number): Observable { + getSharePlaygroundURI(uid: number, dbId: number): Observable { return this.http.post( - `/api/v2/playground/${uid}/databases/${dbId}/dump`, + `/api/v2/playground/${uid}/databases/${dbId}/share`, {} ); } diff --git a/modules/fbs-runner/checker/Dockerfile b/modules/fbs-runner/checker/Dockerfile index 1fb7ba206..5c9d145f9 100644 --- a/modules/fbs-runner/checker/Dockerfile +++ b/modules/fbs-runner/checker/Dockerfile @@ -8,7 +8,7 @@ RUN gradle fbs-runner.checker:installDist FROM eclipse-temurin:18.0.2_9-jre-alpine # Install Docker -RUN apk add --no-cache docker curl +RUN apk add --no-cache docker curl postgresql-client COPY --from=build /build/modules/fbs-runner/checker/build/install/fbs-runner.checker /usr/local/fbs-runner.checker EXPOSE 8081 diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/MainVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/MainVerticle.scala index 9f07ef1ee..721bd49c0 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/MainVerticle.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/MainVerticle.scala @@ -1,7 +1,7 @@ package de.thm.ii.fbs import de.thm.ii.fbs.verticles.HttpVerticle -import de.thm.ii.fbs.verticles.runner.{BashRunnerVerticle, SqlCheckerVerticle, SqlPlaygroundVerticle, SqlRunnerVerticle} +import de.thm.ii.fbs.verticles.runner.{BashRunnerVerticle, SqlCheckerVerticle, SqlPlaygroundShareVerticle, SqlPlaygroundVerticle, SqlRunnerVerticle} import io.vertx.lang.scala.json.JsonObject import io.vertx.lang.scala.{ScalaLogger, ScalaVerticle} import io.vertx.scala.config.{ConfigRetriever, ConfigRetrieverOptions, ConfigStoreOptions} @@ -83,5 +83,13 @@ class MainVerticle extends ScalaVerticle { .setInstances(config.getInteger("SQL_PLAYGROUND_INSTANCES", 1)) vertx.deployVerticleFuture(ScalaVerticle.nameForVerticle[SqlPlaygroundVerticle], sqlPlaygroundVerticleOptions) + + /*Start SQL Playground Share */ + val sqlPlaygroundShareVerticleOptions = DeploymentOptions() + .setConfig(config) + .setWorker(true) + .setInstances(config.getInteger("SQL_PLAYGROUND_SHARE_INSTANCES", 1)) + + vertx.deployVerticleFuture(ScalaVerticle.nameForVerticle[SqlPlaygroundShareVerticle], sqlPlaygroundShareVerticleOptions) } } diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala index ed549fb11..cb0bf7b7a 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/DBOperationsService.scala @@ -18,11 +18,7 @@ abstract case class DBOperationsService(dbName: String, username: String, queryT def initDB(client: JDBCClient, query: String): Future[ResultSet] = { queryFutureWithTimeout(client, query) } -/** - def copyDatabase(targetClient: JDBCClient, targetDBName: String): Future[ResultSet] -*/ - def createUserWithAccess(targetClient: JDBCClient, targetDBName: String, targetHost: String): Future[String] - def copyDatabaseToAnotherInstance(sourceHost: String, sourceDBName: String, targetHost: String, targetDBName: String): Future[Unit] + def createUserWithWriteAccess(client: JDBCClient, skipUserCreation: Boolean = false): Future[String] def createUserIfNotExist(client: SQLConnection, password: String): Future[ResultSet] diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlNativeOperationsService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlNativeOperationsService.scala new file mode 100644 index 000000000..d7fe05136 --- /dev/null +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlNativeOperationsService.scala @@ -0,0 +1,26 @@ +package de.thm.ii.fbs.services.db + +import io.vertx.lang.scala.ScalaLogger + +import scala.concurrent.{ExecutionContext, Future, blocking} +import scala.io.Source +import java.io.PrintWriter +import scala.sys.process.Process + +class PsqlNativeOperationsService( + url: String, + )(implicit ec: ExecutionContext) { + private val logger = ScalaLogger.getLogger(this.getClass.getName) + + def dump(): Future[String] = Future(blocking { + Process(Seq("pg_dump", "--no-acl", "--no-owner", "--inserts", url)).!! + }) + + def restore(dump: String): Future[Boolean] = Future(blocking { + val proc = new ProcessBuilder(Seq("psql", url): _*).start() + new PrintWriter(proc.getOutputStream).write(dump) + proc.getOutputStream.close() + val exit = proc.waitFor() + exit == 0 + }) +} diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala index ec3564f10..de60748c3 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/db/PsqlOperationsService.scala @@ -4,17 +4,15 @@ import de.thm.ii.fbs.types.PsqlPrivileges import io.vertx.lang.scala.json.JsonArray import io.vertx.scala.ext.jdbc.JDBCClient import io.vertx.scala.ext.sql.{ResultSet, SQLConnection} -import scala.sys.process._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future -import scala.sys.process.Process import scala.util.{Failure, Success, Try} class PsqlOperationsService(override val dbName: String, override val username: String, override val queryTimeout: Int) extends DBOperationsService(dbName, username, queryTimeout) { private val WRITE_USER_PRIVILEGES: PsqlPrivileges = - PsqlPrivileges("USAGE, CREATE", "INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER", "USAGE, SELECT, UPDATE") + PsqlPrivileges("USAGE, CREATE", "INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, SELECT", "USAGE, SELECT, UPDATE") private val READ_USER_PRIVILEGES: PsqlPrivileges = PsqlPrivileges("USAGE", "SELECT", "SELECT") @@ -46,10 +44,27 @@ class PsqlOperationsService(override val dbName: String, override val username: override def createUserWithWriteAccess(client: JDBCClient, skipUserCreation: Boolean = false): Future[String] = { val password = if (skipUserCreation) "" else generateUserPassword() - val userCreateQuery = if (skipUserCreation) "" else s"""CREATE USER "$username" WITH ENCRYPTED PASSWORD '$password';""" + createPostgresqlUser(client, username, password).map(_ => password) + } + + def createPostgresqlUser(client: JDBCClient, username: String, password: String): Future[ResultSet] = { + val userCreateQuery = if (username == "" || password == "") { + "" + } else { + s"""DROP USER IF EXISTS "$username"; CREATE USER "$username" WITH ENCRYPTED PASSWORD '$password';""" + } + val writeQuery = buildWriteQuery(username, userCreateQuery) + + client.queryFuture(writeQuery) + } + + def granForUser(client: JDBCClient, username: String): Future[ResultSet] = + client.queryFuture(buildWriteQuery(username)) + + private def buildWriteQuery(username: String, baseQuery: String = "") = { val writeQuery = s""" - |$userCreateQuery + |$baseQuery |REVOKE CREATE ON SCHEMA public FROM PUBLIC; |GRANT CONNECT on DATABASE "$dbName" TO "$username"; |GRANT ${WRITE_USER_PRIVILEGES.schema} ON SCHEMA public TO "$username"; @@ -57,8 +72,7 @@ class PsqlOperationsService(override val dbName: String, override val username: |GRANT ${WRITE_USER_PRIVILEGES.sequence} ON ALL SEQUENCES IN SCHEMA public TO "$username"; |ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ${WRITE_USER_PRIVILEGES.table} ON TABLES TO "$username"; |""".stripMargin - - client.queryFuture(writeQuery).map(_ => password) + writeQuery } override def createUserIfNotExist(client: SQLConnection, password: String): Future[ResultSet] = { @@ -156,41 +170,6 @@ class PsqlOperationsService(override val dbName: String, override val username: client.queryFuture(s"$tables $constrains $views $routines $triggers") } -/** - override def copyDatabase(targetClient: JDBCClient, targetDBName: String): Future[ResultSet] = { - val copyDBQuery = s"CREATE DATABASE $targetDBName WITH TEMPLATE $dbName" - targetClient.queryFuture(copyDBQuery) - } -*/ -def copyDatabaseToAnotherInstance(sourceHost: String, sourceDBName: String, targetHost: String, targetDBName: String): Future[Unit] = Future { - /**Command to dump the database from the source - * missing case where sourcehost has no password*/ - val dumpCommand = s"pg_dump -h $sourceHost -U $username -F c -b -v -f /tmp/$sourceDBName.dump $sourceDBName" - val dumpExitCode = Process(Seq("bash", "-c", dumpCommand), None, "PGPASSWORD" -> "your_source_db_password").! - - if (dumpExitCode != 0) { - throw new Exception(s"Failed to dump the database from $sourceHost") - } - - // Command to restore the dump to the target - val restoreCommand = s"pg_restore -h $targetHost -U $username -d $targetDBName -v /tmp/$sourceDBName.dump" - val restoreExitCode = Process(Seq("bash", "-c", restoreCommand), None, "PGPASSWORD" -> "your_target_db_password").! - - if (restoreExitCode != 0) { - throw new Exception(s"Failed to restore the database to $targetHost") - } -} - - override def createUserWithAccess(targetClient: JDBCClient, targetDBName: String, targetHost: String): Future[String] = { - val password = generateUserPassword() - val userCreateQuery = s"CREATE USER $username WITH PASSWORD '$password';" - val permissionsQuery = s"GRANT ALL PRIVILEGES ON DATABASE $targetDBName TO $username;" - - for { - _ <- targetClient.queryFuture(userCreateQuery) - _ <- targetClient.queryFuture(permissionsQuery) - } yield s"postgresql://$username:$password@$targetHost/$targetDBName" - override def queryFutureWithTimeout(client: JDBCClient, sql: String): Future[ResultSet] = { client.getConnectionFuture().flatMap(con => { con.queryFuture(s"SET statement_timeout = ${queryTimeout*1000};").flatMap(_ => { diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala index 6729762d7..1445d096e 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundService.scala @@ -2,11 +2,11 @@ package de.thm.ii.fbs.services.runner import de.thm.ii.fbs.services.ExtendedResultsService import de.thm.ii.fbs.services.db.{DBOperationsService, PsqlOperationsService} -import de.thm.ii.fbs.types.{OutputJsonStructure, SharePlaygroundArgs, SqlPlaygroundRunArgs} +import de.thm.ii.fbs.types.{OutputJsonStructure, SqlPlaygroundRunArgs} import de.thm.ii.fbs.util.{DBTypes, DatabaseInformationService, PlaygroundDBConnections, SqlPlaygroundMode} import io.vertx.core.json.JsonObject import io.vertx.scala.ext.sql.ResultSet -import io.vertx.scala.ext.jdbc.JDBCClient + import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.util.{Failure, Success} @@ -21,11 +21,6 @@ object SQLPlaygroundService { OutputJsonStructure("triggers", Option("manipulation")) ) -// change this function with dump function - def test(): String = { - s"Test is working" - } - def isPlaygroundResult(res: JsonObject): Boolean = res.getString("resultType", "").equals(PLAYGROUND_RESULT_TYPE) /** @@ -70,18 +65,12 @@ class SQLPlaygroundService(val sqlRunArgs: SqlPlaygroundRunArgs, val con: Playgr private def initDBOperations(): DBOperationsService = { // Currently only PostgresSql is Supported if (!isPsql) throw new Error("Invalid DBType. Currently only Psql is Supported") + val dbName = s"playground_db_${sqlRunArgs.database.id.toString}" val username = s"playground_user_${sqlRunArgs.user.id.toString}" new PsqlOperationsService(dbName, username, queryTimeout) } - def copyDBAndCreateUser(sourceHost: String, targetHost: String, targetClient: JDBCClient): Future[String] = { - val dbOperations = initDBOperations() - for { - _ <- dbOperations.copyDatabaseToAnotherInstance(sourceHost, dbOperations.dbName, targetHost, dbOperations.dbName) - uri <- dbOperations.createUserWithAccess(targetClient, dbOperations.dbName, targetHost) - } yield uri - } def executeStatement(): Future[(ResultSet, ResultSet)] = { val dbOperations = initDBOperations() val deleteDatabase = SqlPlaygroundMode.shouldDeleteDatabase(sqlRunArgs.mode) diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala new file mode 100644 index 000000000..51fe564d2 --- /dev/null +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala @@ -0,0 +1,5 @@ +package de.thm.ii.fbs.services.runner + +class SQLPlaygroundShareService { + +} diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala index 93f1ce086..48496bd8c 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLRunnerService.scala @@ -9,9 +9,7 @@ import de.thm.ii.fbs.util.Secrets.getSHAStringFromNow import de.thm.ii.fbs.util.{DBConnections, DBTypes, RunnerException} import io.vertx.core.json.DecodeException import io.vertx.lang.scala.json.JsonObject -import io.vertx.scala.core.Vertx.vertx import io.vertx.scala.ext.sql.ResultSet -import io.vertx.scala.ext.jdbc.JDBCClient import java.sql.SQLException import scala.concurrent.ExecutionContext.Implicits.global @@ -109,8 +107,6 @@ class SQLRunnerService(val sqlRunArgs: SqlRunArgs, val solutionCon: DBConnection private val configDbExt = s"${getSHAStringFromNow()}_c" private val submissionDbExt = s"${getSHAStringFromNow()}_s" - - private def isVariable(taskQuery: TaskQuery): Boolean = taskQuery.order != null && taskQuery.order.equalsIgnoreCase("variable") @@ -141,31 +137,6 @@ class SQLRunnerService(val sqlRunArgs: SqlRunArgs, val solutionCon: DBConnection dbOperation } - /** - def copyPlaygroundDBAndCreateUser(targetDBHost: String, targetDBPort: Int, dbUser: String, dbPassword: String, originalDBName: String, targetDBName: String): Future[String] = { - // Create a client for the target database - val targetJDBCClient = JDBCClient.createNonShared(vertx, new JsonObject() - .put("url", s"jdbc:postgresql://$targetDBHost:$targetDBPort/$targetDBName") - .put("user", dbUser) - .put("password", dbPassword) - ) - - // Initialize a new instance of PsqlOperationsService with the original database name, user, and password - val targetDBOperations = new PsqlOperationsService(originalDBName, dbUser, queryTimout) - - // First, copy the database to the new instance - val copyDBFuture = targetDBOperations.copyDatabase(targetJDBCClient, targetDBName) - - // Then, create a new user with access to the copied database - val createUserFuture = copyDBFuture.flatMap { _ => - targetDBOperations.createUserWithAccess(targetJDBCClient, targetDBName) - } - // Return the PostgreSQL URI - createUserFuture.map { newUserAndPassword => - s"jdbc:postgresql://$targetDBHost:$targetDBPort/$targetDBName?user=$newUserAndPassword._1&password=$newUserAndPassword._2" - } - } -*/ private def buildName(nameExtension: String): String = s"${sqlRunArgs.submissionId}_${sqlRunArgs.runnerId}_$nameExtension" /** @@ -304,19 +275,4 @@ class SQLRunnerService(val sqlRunArgs: SqlRunArgs, val solutionCon: DBConnection (msg, success, correctResults) } - - /** def copyPlaygroundDBAndCreateUser(targetDBHost: String, targetDBPort: Int): Future[String] = { - val targetDBName = s"${dbName}_copy" - val targetJDBCClient = JDBCClient.createNonShared(vertx, new JsonObject() - .put("url", s"jdbc:postgresql://$targetDBHost:$targetDBPort/") - .put("user", username) - .put("password", password) - ) - - for { - _ <- copyDatabase(targetJDBCClient, targetDBName) - uri <- createUserWithAccess(targetJDBCClient, targetDBName) - } yield uri - } -**/ } diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala deleted file mode 100644 index c3e114c51..000000000 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SharePlaygroundArgs.scala +++ /dev/null @@ -1,7 +0,0 @@ -package de.thm.ii.fbs.types - -import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonProperty} - -@JsonIgnoreProperties(ignoreUnknown = true) // Ignore runner Type -case class SharePlaygroundArgs(@JsonProperty("user") user: User, - @JsonProperty("database") database: Database) \ No newline at end of file diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SqlPlaygroundShareArgs.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SqlPlaygroundShareArgs.scala new file mode 100644 index 000000000..277213ea5 --- /dev/null +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/types/SqlPlaygroundShareArgs.scala @@ -0,0 +1,11 @@ +package de.thm.ii.fbs.types + +import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonProperty} + +@JsonIgnoreProperties(ignoreUnknown = true) // Ignore runner Type +case class SqlPlaygroundShareArgs(@JsonProperty("executionId") executionId: Int, + @JsonProperty("user") user: User, + @JsonProperty("database") database: Database, + @JsonProperty("id") id: String, + @JsonProperty("password") password: String, + ) diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/util/PlaygroundDBConnections.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/util/PlaygroundDBConnections.scala index 8467d621e..28f599415 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/util/PlaygroundDBConnections.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/util/PlaygroundDBConnections.scala @@ -2,6 +2,7 @@ package de.thm.ii.fbs.util import de.thm.ii.fbs.services.db.DBOperationsService import de.thm.ii.fbs.types.SqlPoolWithConfig +import io.vertx.lang.scala.ScalaLogger import io.vertx.scala.core.Vertx import io.vertx.scala.ext.jdbc.JDBCClient @@ -33,8 +34,12 @@ class PlaygroundDBConnections(override val vertx: Vertx, override val sqlPoolWit override protected def initPool (username: String, dbOperations: DBOperationsService, dbConfig: String, allowUserWrite: Boolean, skipDBInt: Boolean): Future[Option[JDBCClient]] = { - // Generate Password from UserID to avoid storing the password - val password = Secrets.generateHMAC(dbOperations.username) + val password = if (sqlPoolWithConfig.config.getString("user") == username) { + sqlPoolWithConfig.config.getString("password") + } else { + // Generate Password from UserID to avoid storing the password + Secrets.generateHMAC(dbOperations.username) + } dbOperations.createUserIfNotExist(operationCon.get, password).flatMap(_ => { dbOperations.createDBIfNotExist(operationCon.get).flatMap(dbWasCreated => { diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala index 1ba0fbdfe..336dfb86f 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/HttpVerticle.scala @@ -2,7 +2,7 @@ package de.thm.ii.fbs.verticles import de.thm.ii.fbs.services.runner.SQLPlaygroundService import de.thm.ii.fbs.verticles.HttpVerticle.SEND_COMPLETION -import de.thm.ii.fbs.verticles.runner.{BashRunnerVerticle, SharePlaygroundVerticle, SqlCheckerVerticle, SqlPlaygroundVerticle, SqlRunnerVerticle} +import de.thm.ii.fbs.verticles.runner.{BashRunnerVerticle, SqlCheckerVerticle, SqlPlaygroundShareVerticle, SqlPlaygroundVerticle, SqlRunnerVerticle} import io.vertx.lang.scala.json.JsonObject import io.vertx.lang.scala.{ScalaLogger, ScalaVerticle} import io.vertx.scala.core.eventbus.Message @@ -94,8 +94,8 @@ class HttpVerticle extends ScalaVerticle { case "sql-playground" => vertx.eventBus().send(SqlPlaygroundVerticle.RUN_ADDRESS, body) ctx.response().setStatusCode(202).end() - case "share" => - vertx.eventBus().send(SharePlaygroundVerticle.RUN_ADDRESS, body) + case "sql-playground-share" => + vertx.eventBus().send(SqlPlaygroundShareVerticle.RUN_ADDRESS, body) ctx.response().setStatusCode(202).end() case _ => ctx.response().setStatusCode(404).end("Invalid Runner Type") } diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala deleted file mode 100644 index ac9cb32cf..000000000 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SharePlaygroundVerticle.scala +++ /dev/null @@ -1,43 +0,0 @@ -package de.thm.ii.fbs.verticles.runner - -import de.thm.ii.fbs.services.runner.SQLPlaygroundService -import de.thm.ii.fbs.types._ -import de.thm.ii.fbs.verticles.runner.SharePlaygroundVerticle.RUN_ADDRESS -import io.vertx.lang.scala.{ScalaLogger, ScalaVerticle} -import io.vertx.scala.core.eventbus.Message -import io.vertx.core.json.JsonObject - -import scala.concurrent.Future - -class SharePlaygroundVerticle extends ScalaVerticle { - private val logger = ScalaLogger.getLogger(getClass.getName) - - override def startFuture(): Future[_] = { - vertx.eventBus().consumer(RUN_ADDRESS, handleTest).completionFuture() - } - - //placeholder function - private def handleTest(msg: Message[JsonObject]): Future[Unit] = Future { - val argsJson = msg.body() - - val userJson = argsJson.getJsonObject("user") - val databaseJson = argsJson.getJsonObject("database") - - val userId = userJson.getInteger("id") - val username = userJson.getString("username") - val databaseName = databaseJson.getString("name") - val databaseid = databaseJson.getInteger("id") - - val user = new User(userId, username) - val database = Database(databaseid, databaseName) - - val args = SharePlaygroundArgs(user, database) - - val response = SQLPlaygroundService.test() - msg.reply(response) - } -} - -object SharePlaygroundVerticle { - val RUN_ADDRESS = "de.thm.ii.fbs.runner.share" -} \ No newline at end of file diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundShareVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundShareVerticle.scala new file mode 100644 index 000000000..eb34f3862 --- /dev/null +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundShareVerticle.scala @@ -0,0 +1,165 @@ +package de.thm.ii.fbs.verticles.runner + +import de.thm.ii.fbs.services.db.{PsqlNativeOperationsService, PsqlOperationsService} +import de.thm.ii.fbs.services.runner.SQLPlaygroundService.PLAYGROUND_RESULT_TYPE +import de.thm.ii.fbs.types._ +import de.thm.ii.fbs.util.DBTypes.PSQL_CONFIG_KEY +import de.thm.ii.fbs.util.{Metrics, PlaygroundDBConnections} +import de.thm.ii.fbs.verticles.HttpVerticle +import de.thm.ii.fbs.verticles.runner.SqlPlaygroundShareVerticle.RUN_ADDRESS +import io.vertx.core.json.JsonObject +import io.vertx.lang.scala.{ScalaLogger, ScalaVerticle} +import io.vertx.scala.core.eventbus.Message +import io.vertx.scala.ext.jdbc.JDBCClient + +import java.net.URI +import java.sql.SQLException +import java.util.Date +import scala.concurrent.Future +import scala.util.{Failure, Success} + +/** + * Object that stores all static vars for the SqlPlaygroundVerticle + */ +object SqlPlaygroundShareVerticle { + /** Event Bus Address to start an runner */ + val RUN_ADDRESS = "de.thm.ii.fbs.runner.sqlPlayground.share" +} + +/** + * Verticle that starts the SqlPlayground + */ +class SqlPlaygroundShareVerticle extends ScalaVerticle { + private val logger = ScalaLogger.getLogger(this.getClass.getName) + private var sqlPools = Map[String, SqlPoolWithConfig]() + private val meter = Metrics.openTelemetry.meterBuilder("de.thm.mni.ii.fbs.verticles.runner.playground.share").build() + private val processingCounter = meter.upDownCounterBuilder("processingCount").setDescription("Processing Requests").build() + private val processingTimeCounter = meter.histogramBuilder("processingTime").ofLongs().setDescription("Time for processing").setUnit("ms").build() + private val errorCounter = meter.counterBuilder("errorCount").setDescription("Error Count").build() + + /** + * start SqlRunnerVerticle + * + * @return vertx Future + */ + override def startFuture(): Future[_] = { + val psqlDataSource = s"${PSQL_CONFIG_KEY}_playground_share" + val psqlConfig = new JsonObject() + .put("user", config.getString("SQL_PLAYGROUND_SHARE_PSQL_SERVER_USERNAME", "root")) + .put("password", config.getString("SQL_PLAYGROUND_SHARE_PSQL_SERVER_PASSWORD", "")) + .put("url", config.getString("SQL_PLAYGROUND_SHARE_PSQL_SERVER_URL", "jdbc:postgresql://localhost:5432")) + .put("max_pool_size", config.getInteger("SQL_PLAYGROUND_STARING_INSTANCES", 256)) + .put("driver_class", "org.postgresql.Driver") + .put("max_idle_time", config.getInteger("SQL_MAX_IDLE_TIME", 10)) + .put("dataSourceName", psqlDataSource) + val psqlPool = JDBCClient.createShared(vertx, psqlConfig, psqlDataSource) + sqlPools += (PSQL_CONFIG_KEY -> SqlPoolWithConfig(psqlPool, psqlConfig)) + + vertx.eventBus().consumer(RUN_ADDRESS, sqlPlaygroundShare).completionFuture() + } + + private def buildSourceUrl(database: String): String = { + val base = config.getString("SQL_PLAYGROUND_PSQL_SERVER_URL", "jdbc:postgresql://localhost:5432").split(":", 2)(1) + val username = config.getString("SQL_PLAYGROUND_PSQL_SERVER_USERNAME", "postgresql") + val password = config.getString("SQL_PLAYGROUND_PSQL_SERVER_PASSWORD", "") + val url = new URI(base) + s"postgresql://${username}:${password}@${url.getHost}:${url.getPort}/${database}" + } + + private def copyDatabaseAndCreateUser(runArgs: SqlPlaygroundShareArgs): Future[Unit] = { + val sourceURI = buildSourceUrl(s"playground_db_${runArgs.database.id}") + new PsqlNativeOperationsService(sourceURI).dump().flatMap(dump => { + val ops = new PsqlOperationsService( + runArgs.id, + config.getString("SQL_PLAYGROUND_SHARE_PSQL_SERVER_USERNAME", "root"), + queryTimeout = config.getInteger("SQL_QUERY_TIMEOUT_S", 60) + ) + val conn = getConnection(runArgs).get + conn.initCon(ops).flatMap(_ => { + ops.createDB(conn.operationCon.get).flatMap(_ => { + ops.createPostgresqlUser(conn.queryCon.get, runArgs.id, runArgs.password).flatMap(_ => { + ops.queryFutureWithTimeout(conn.queryCon.get, dump).map(_ => Unit) + }) + }) + }) + }) + } + + /*private def deleteDatabase(id: String): Future[Unit] = Future { + val conn = getConnection(runArgs).get + + val ops = new PsqlOperationsService(id, id, queryTimeout = 0) + ops.createDB(conn.operationCon.get) + }*/ + + private def sqlPlaygroundShare(msg: Message[JsonObject]): Future[Unit] = Future { + val msgBody = msg.body() + if (msgBody.getBoolean("delete")) { + Future.unit + //deleteDatabase(msgBody.getString("id")) + } else { + logger.info("sharing") + val runArgs = msgBody.mapTo(classOf[SqlPlaygroundShareArgs]) + + processingCounter.add(1) + val startTime = new Date().getTime + val end = (failure: Boolean) => { + val endTime = new Date().getTime + processingTimeCounter.record(endTime - startTime) + processingCounter.add(-1) + if (failure) errorCounter.add(1) + } + + try { + copyDatabaseAndCreateUser(runArgs).onComplete { + case Success(_) => { + logger.info(s"Database copied") + end(false) + } + case Failure(ex) => { + end(true) + logger.error("Error in copying database and creating user", ex) + } + } + } catch { + case e: Throwable => + end(true) + handleError(runArgs, e) + } + } + } + + private def getConnection(runArgs: SqlPlaygroundShareArgs): Option[PlaygroundDBConnections] = { + try { + val poolWithConfig = sqlPools.getOrElse(runArgs.database.dbType.toLowerCase, sqlPools.default(PSQL_CONFIG_KEY)) + Option(new PlaygroundDBConnections(vertx, poolWithConfig)) + } catch { + case e: Throwable => + handleError(runArgs, e) + None + } + } + + def buildResult(runArgs: SqlPlaygroundShareArgs): AnyRef = { + val res = new JsonObject() + + res.put("executionId", runArgs.executionId) + .put("resultType", PLAYGROUND_RESULT_TYPE) + + res + } + + private def handleError(runArgs: SqlPlaygroundShareArgs, e: Throwable, mg: String = "Die Ausführung des Statements ist fehlgeschlagen."): Unit = { + logger.error(s"Playground sharing failed", e) + vertx.eventBus().send(HttpVerticle.SEND_COMPLETION, Option(buildResult(runArgs))) + } + + private def getSQLErrorMsg(runArgs: SqlPlaygroundShareArgs, ex: SQLException): String = { + // Remove DB Name from Error message + var errorMsg = ex.getMessage.replaceAll("playground_db_[0-9]+", runArgs.database.name) + // Remove Username Name from Error message + errorMsg = errorMsg.replaceAll("'playground_user_[0-9]+'", s"'${runArgs.user.username}'") + + s"Es gab eine SQLException: $errorMsg" + } +} diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala index 4b7f7bd85..5ddc84602 100644 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala +++ b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/verticles/runner/SqlPlaygroundVerticle.scala @@ -1,7 +1,6 @@ package de.thm.ii.fbs.verticles.runner -import de.thm.ii.fbs.services.db.PsqlOperationsService -import de.thm.ii.fbs.services.runner.{SQLPlaygroundService} +import de.thm.ii.fbs.services.runner.SQLPlaygroundService import de.thm.ii.fbs.types._ import de.thm.ii.fbs.util.DBTypes.PSQL_CONFIG_KEY import de.thm.ii.fbs.util.{Metrics, PlaygroundDBConnections} @@ -19,18 +18,18 @@ import scala.concurrent.Future import scala.util.{Failure, Success} /** - * Object that stores all static vars for the SqlPlaygroundVerticle - */ + * Object that stores all static vars for the SqlPlaygroundVerticle + */ object SqlPlaygroundVerticle { /** Event Bus Address to start an runner */ val RUN_ADDRESS = "de.thm.ii.fbs.runner.sqlPlayground" } /** - * Verticle that starts the SqlPlayground - * - * @author Max Stephan - */ + * Verticle that starts the SqlPlayground + * + * @author Max Stephan + */ class SqlPlaygroundVerticle extends ScalaVerticle { private val logger = ScalaLogger.getLogger(this.getClass.getName) private var sqlPools = Map[String, SqlPoolWithConfig]() @@ -40,10 +39,10 @@ class SqlPlaygroundVerticle extends ScalaVerticle { private val errorCounter = meter.counterBuilder("errorCount").setDescription("Error Count").build() /** - * start SqlRunnerVerticle - * - * @return vertx Future - */ + * start SqlRunnerVerticle + * + * @return vertx Future + */ override def startFuture(): Future[_] = { val psqlDataSource = s"${PSQL_CONFIG_KEY}_playground" val psqlConfig = new JsonObject() @@ -60,30 +59,6 @@ class SqlPlaygroundVerticle extends ScalaVerticle { vertx.eventBus().consumer(RUN_ADDRESS, startSqlPlayground).completionFuture() } - private def copyDatabaseAndCreateUser(runArgs: SqlPlaygroundRunArgs): Future[String] = { - // Extract sqlRunArgs from the message - - - // Get the PlaygroundDBConnections - val con = getConnection(runArgs).getOrElse(throw new Exception("Failed to get database connection")) - - // Get the query timeout from config - val queryTimeout = config.getInteger("SQL_QUERY_TIMEOUT_S", 10) - - // Create an instance of SQLPlaygroundService - val sqlPlaygroundService = new SQLPlaygroundService(runArgs, con, queryTimeout) - - // Extract source and target host - val sourceHost = config.getString("PSQL_SERVER_URL", "jdbc:postgresql://localhost:5432").split("//")(1).split("/")(0) - val targetHost = config.getString("PSQL_SHARING_SERVER_URL", "jdbc:postgresql://psql-sharing:5432").split("//")(1).split("/")(0) - - // Get the JDBC client for the target database - val targetClient = sqlPools.getOrElse(PSQL_CONFIG_KEY, throw new Exception("PSQL config not found")).pool - - // Call the method to copy database and create user - sqlPlaygroundService.copyDBAndCreateUser(sourceHost, targetHost, targetClient) - } - private def startSqlPlayground(msg: Message[JsonObject]): Future[Unit] = Future { val runArgs = msg.body().mapTo(classOf[SqlPlaygroundRunArgs]) @@ -108,11 +83,6 @@ class SqlPlaygroundVerticle extends ScalaVerticle { case Failure(_) => end(true) } - // Call the method to copy database and create user - copyDatabaseAndCreateUser(runArgs).onComplete { - case Success(uri) => logger.info(s"Database copied, new URI: $uri") - case Failure(ex) => logger.error("Error in copying database and creating user", ex) - } } else { end(false) } From e20b4775c13e45f4fd903e79dad3ba620dc4796b Mon Sep 17 00:00:00 2001 From: Jonas Kuche Date: Thu, 23 Nov 2023 09:15:28 +0100 Subject: [PATCH 5/5] chore: fix style --- .../de/thm/ii/fbs/controller/v2/RunnerApiController.kt | 3 +-- .../ii/fbs/model/v2/playground/SqlPlaygroundShare.kt | 4 ++-- .../services/v2/checker/SqlPlaygroundCheckerService.kt | 9 +++++---- .../kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt | 2 +- .../services/v2/misc/SharePlaygroundCleanupService.kt | 2 +- .../db-control-db-overview.component.ts | 2 +- .../web/src/app/service/sql-playground.service.ts | 10 ++++++++-- .../services/runner/SQLPlaygroundShareService.scala | 5 ----- 8 files changed, 19 insertions(+), 18 deletions(-) delete mode 100644 modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt index 2d9f9d9c3..be5b16e7b 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/controller/v2/RunnerApiController.kt @@ -11,7 +11,7 @@ import de.thm.ii.fbs.services.v2.persistence.SqlPlaygroundQueryRepository import de.thm.ii.fbs.utils.v2.exceptions.NotFoundException import org.springframework.data.repository.findByIdOrNull import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.* // ktlint-disable no-wildcard-imports @RestController class RunnerApiController( @@ -28,7 +28,6 @@ class RunnerApiController( queryRepository.save(query) } - private fun updateAllEntity(query: SqlPlaygroundQuery, result: SqlPlaygroundRunnerResult) { // Do not update the entity if the query had an error, as the checker will not return db information if there was an error if (result.error) return diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt index a6effe7da..ee8540099 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/model/v2/playground/SqlPlaygroundShare.kt @@ -1,6 +1,6 @@ package de.thm.ii.fbs.model.v2.playground import java.time.LocalDateTime -import javax.persistence.* +import javax.persistence.* // ktlint-disable no-wildcard-imports @Entity @Table(name = "sql_playground_share") @@ -12,5 +12,5 @@ class SqlPlaygroundShare( val creationTime: LocalDateTime, @Id @Column(name = "database_id") - val id: Int? = null, + val id: Int? = null ) diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt index eb92a6b89..adf3dcf46 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/checker/SqlPlaygroundCheckerService.kt @@ -1,6 +1,6 @@ package de.thm.ii.fbs.services.v2.checker -import de.thm.ii.fbs.model.v2.checker.* +import de.thm.ii.fbs.model.v2.checker.* // ktlint-disable no-wildcard-imports import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundDatabase import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundQuery import de.thm.ii.fbs.model.v2.playground.SqlPlaygroundShare @@ -22,7 +22,7 @@ class SqlPlaygroundCheckerService( @Value("\${services.sqlPlayground.share.publicPort}") private val publicSharePort: Int, private val sqlSharePlaygroundShareRepository: SqlSharePlaygroundShareRepository, - private val idService: IdService, + private val idService: IdService ) : RemoteCheckerV2Service(insecure, masterRunnerURL) { fun submit(query: SqlPlaygroundQuery) { @@ -50,9 +50,10 @@ class SqlPlaygroundCheckerService( RunnerDatabase(db.id!!, db.name), id, token - )) + ) + ) sqlSharePlaygroundShareRepository.save(SqlPlaygroundShare(db, creationTime, db.id!!)) - return "postgresql://${id}:${token}@${publicShareHost}:${publicSharePort}/${id}" + return "postgresql://$id:$token@$publicShareHost:$publicSharePort/$id" } fun deleteDatabaseShare(share: SqlPlaygroundShare) { diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt index 7a20b62e2..cc88d3554 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/IdService.kt @@ -9,7 +9,7 @@ class IdService( @Value("\${services.ids.salt}") salt: String, @Value("\${services.ids.length}") - length: Int, + length: Int ) { private val hashids = Hashids(salt, length) diff --git a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt index 222547ff6..07ee94469 100644 --- a/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt +++ b/modules/fbs-core/api/src/main/kotlin/de/thm/ii/fbs/services/v2/misc/SharePlaygroundCleanupService.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime @Service class SharePlaygroundCleanupService( private val sqlSharePlaygroundShareRepository: SqlSharePlaygroundShareRepository, - private val sqlPlaygroundCheckerService: SqlPlaygroundCheckerService, + private val sqlPlaygroundCheckerService: SqlPlaygroundCheckerService ) { @Scheduled(fixedDelayString = "PT1H") // Runs every hour fun cleanupExpiredShares() { diff --git a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts index a2ca67989..35ff47d6f 100644 --- a/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts +++ b/modules/fbs-core/web/src/app/page-components/sql-playground/db-control-panel/db-control-db-overview/db-control-db-overview.component.ts @@ -195,7 +195,7 @@ export class DbControlDbOverviewComponent implements OnInit { uri: share.url, }, }); - }) + }); } addDb() { diff --git a/modules/fbs-core/web/src/app/service/sql-playground.service.ts b/modules/fbs-core/web/src/app/service/sql-playground.service.ts index ceb278d95..132267362 100644 --- a/modules/fbs-core/web/src/app/service/sql-playground.service.ts +++ b/modules/fbs-core/web/src/app/service/sql-playground.service.ts @@ -5,7 +5,10 @@ import { Constraint } from "../model/sql_playground/Constraint"; import { Database } from "../model/sql_playground/Database"; import { Routine } from "../model/sql_playground/Routine"; import { SQLExecuteResponse } from "../model/sql_playground/SQLExecuteResponse"; -import {SQLPlaygroundShare, SQLResponse} from "../model/sql_playground/SQLResponse"; +import { + SQLPlaygroundShare, + SQLResponse, +} from "../model/sql_playground/SQLResponse"; import { Table } from "../model/sql_playground/Table"; import { Trigger } from "../model/sql_playground/Trigger"; import { View } from "../model/sql_playground/View"; @@ -83,7 +86,10 @@ export class SqlPlaygroundService { * Get Database Temp URI * @return the temporary database URI */ - getSharePlaygroundURI(uid: number, dbId: number): Observable { + getSharePlaygroundURI( + uid: number, + dbId: number + ): Observable { return this.http.post( `/api/v2/playground/${uid}/databases/${dbId}/share`, {} diff --git a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala b/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala deleted file mode 100644 index 51fe564d2..000000000 --- a/modules/fbs-runner/checker/src/main/scala/de/thm/ii/fbs/services/runner/SQLPlaygroundShareService.scala +++ /dev/null @@ -1,5 +0,0 @@ -package de.thm.ii.fbs.services.runner - -class SQLPlaygroundShareService { - -}