Skip to content

Commit

Permalink
[Playground] Add Database sharing (#1525)
Browse files Browse the repository at this point in the history
* 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

* 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 <Zitrone44@users.noreply.github.com>

* 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 <Zitrone44@users.noreply.github.com>

* feat(playground): add database sharing

* chore: fix style

---------

Co-authored-by: Khaled Trabelsi <131378411+trKhaled@users.noreply.github.com>
Co-authored-by: Youssef Mellouli <49683272+ymll58@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 23, 2023
1 parent d2eb7d4 commit e4bb270
Show file tree
Hide file tree
Showing 33 changed files with 579 additions and 25 deletions.
1 change: 1 addition & 0 deletions modules/fbs-core/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -72,6 +73,14 @@ class PlaygroundController(
return databaseRepository.save(db)
}

@PostMapping("/{dbId}/share")
@ResponseBody
fun createPlaygroundShare(@CurrentToken currentToken: LegacyToken, @PathVariable("dbId") dbId: Int): SqlPlaygroundShareResponse {
val currentActiveDb = databaseRepository.findByOwner_IdAndIdAndDeleted(currentToken.id, dbId, false) ?: throw NotFoundException()
val url = sqlPlaygroundCheckerService.shareDatabase(currentActiveDb)
return SqlPlaygroundShareResponse(url)
}

@PostMapping("/{dbId}/reset")
@ResponseBody
@ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.* // ktlint-disable no-wildcard-imports

@RestController
class RunnerApiController(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
SQL_PLAYGROUND_SHARE("sql-playground-share")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.thm.ii.fbs.model.v2.checker

import com.fasterxml.jackson.annotation.JsonProperty

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.SQL_PLAYGROUND_SHARE)
) : RunnerArguments()
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.thm.ii.fbs.model.v2.playground
import java.time.LocalDateTime
import javax.persistence.* // ktlint-disable no-wildcard-imports

@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
)
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
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.* // 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
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
import java.util.*

@Service
class SqlPlaygroundCheckerService(
@Value("\${services.masterRunner.insecure}")
insecure: Boolean,
@Value("\${services.masterRunner.url}")
private val masterRunnerURL: String
private val masterRunnerURL: String,
@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) {
Expand All @@ -31,6 +39,33 @@ class SqlPlaygroundCheckerService(
)
}

fun shareDatabase(db: SqlPlaygroundDatabase): String {
val id = idService.encode(db.id!!)
val token = UUID.randomUUID().toString()
val creationTime = LocalDateTime.now()
this.sendToRunner(
SqlPlaygroundShareArguments(

RunnerUser(db.owner.id!!, db.owner.username),
RunnerDatabase(db.id!!, db.name),
id,
token
)
)
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) {
this.sendToRunner(
SqlPlaygroundRunnerDeleteArguments(
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
}
Original file line number Diff line number Diff line change
@@ -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<SqlPlaygroundShare> = sqlSharePlaygroundShareRepository.findAllByCreationTimeLessThan(now.minusDays(1))
expiredShares.forEach { share ->
sqlPlaygroundCheckerService.deleteDatabaseShare(share)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<SqlPlaygroundShare, String> {
fun findAllByCreationTimeLessThan(creationTime: LocalDateTime): List<SqlPlaygroundShare>
}
7 changes: 7 additions & 0 deletions modules/fbs-core/api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions modules/fbs-core/web/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -213,6 +214,7 @@ export const httpInterceptorProviders = [
ExportTasksDialogComponent,
HighlightedInputComponent,
MathInputComponent,
SharePlaygroundLinkDialogComponent,
FbsModellingComponent,
LanguageMenuComponent,
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="container">
<div matDialogTitle>Temporäre Datenbank URI</div>
<mat-dialog-content style="max-height: 100%; overflow: hidden">
<mat-card-content class="content">
<mat-label>
<h3>{{ data.message }}</h3>
</mat-label>
<div class="uri">
<mat-label
><i>{{ data.uri }}</i></mat-label
>
<button mat-icon-button (click)="copyURI()" matTooltip="URI kopieren">
<mat-icon>content_copy</mat-icon>
</button>
</div>
</mat-card-content>
</mat-dialog-content>
<mat-dialog-actions class="actions">
<button mat-flat-button color="warn" (click)="closeDialog()">
Abbrechen
</button>
</mat-dialog-actions>
</div>
Original file line number Diff line number Diff line change
@@ -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;
}
}


Original file line number Diff line number Diff line change
@@ -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<SharePlaygroundLinkDialogComponent>,
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 });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ interface ResponseTable {
head: string[];
rows: string[];
}

export interface SQLPlaygroundShare {
url: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,14 @@
| i18nextEager
}}
</button>

<button
mat-raised-button
class="url-btn"
(click)="getTempURI()"
[disabled]="collaborativeMode"
>
Verbindungs-URL anfordern
</button>
</div>
</div>
Loading

0 comments on commit e4bb270

Please sign in to comment.