ApplicationResource.kt

package at.htl.beeyond.resource

import at.htl.beeyond.dto.CustomApplicationDto
import at.htl.beeyond.dto.DenyMessageDto
import at.htl.beeyond.dto.TemplateApplicationDto
import at.htl.beeyond.entity.*
import at.htl.beeyond.service.DeploymentService
import at.htl.beeyond.service.NamespaceService
import io.quarkus.hibernate.orm.panache.PanacheEntityBase
import java.time.LocalDateTime
import java.util.stream.Collectors
import javax.annotation.security.RolesAllowed
import javax.inject.Inject
import javax.transaction.Transactional
import javax.ws.rs.*
import javax.ws.rs.core.Context
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.SecurityContext

@Path("/application")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
class ApplicationResource {

    @Inject
    lateinit var deploymentService: DeploymentService

    @Inject
    lateinit var namespaceService: NamespaceService

    @GET
    @RolesAllowed(value = ["student", "teacher"])
    @Transactional
    fun getAll(@Context ctx: SecurityContext): Response? {
        val mapToDto = { o: PanacheEntityBase? ->
            if (o is CustomApplication) {
                CustomApplicationDto(o)
            } else {
                TemplateApplicationDto(o as TemplateApplication)
            }
        }

        val applications = if (ctx.isUserInRole("teacher")) {
            Application.streamAll<Application>().map(mapToDto).collect(Collectors.toList<Any>())
        } else {
            Application.streamAll<Application>().filter {
                it.owner.name == ctx.userPrincipal.name
            }.map(mapToDto).collect(Collectors.toList<Any>())
        }

        return Response.ok(applications).build()
    }

    @GET
    @Path("/{id}")
    @RolesAllowed(value = ["teacher", "student"])
    @Transactional
    fun getApplicationById(@PathParam("id") id: Long?, @Context ctx: SecurityContext): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(Response.Status.NOT_FOUND).build()

        if (!ctx.isUserInRole("teacher") && application.owner.name != ctx.userPrincipal.name) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }

        return if (application is CustomApplication) {
            Response.ok(CustomApplicationDto(application)).build()
        } else {
            Response.ok(TemplateApplicationDto((application as TemplateApplication))).build()
        }
    }

    @PATCH
    @Path("/approve/{id}")
    @RolesAllowed("teacher")
    @Transactional
    fun approveApplication(@PathParam("id") id: Long?): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(Response.Status.NOT_FOUND).build()

        return if (application.status == ApplicationStatus.PENDING) {
            deploy(application)

            application.namespace.users.forEach {
                val notification = Notification(
                    it,
                    "Application has been accepted!",
                    "",
                    NotificationStatus.POSITIVE,
                    "application",
                    application.id
                )
                notification.persist()
            }

            Response.ok().build()
        } else {
            Response.status(422).entity("Application is not in state " + ApplicationStatus.PENDING).build()
        }
    }

    @PATCH
    @Path("/start/{id}")
    @RolesAllowed(value = ["teacher", "student"])
    @Transactional
    fun startApplication(@PathParam("id") id: Long?, @Context ctx: SecurityContext): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(Response.Status.NOT_FOUND).build()

        if (!ctx.isUserInRole("teacher") && application.owner.name != ctx.userPrincipal.name) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }

        return if (application.status == ApplicationStatus.STOPPED) {
            deploy(application)

            application.namespace.users.forEach {
                val notification = Notification(
                    it,
                    "Application has been started!",
                    "",
                    NotificationStatus.POSITIVE,
                    "application",
                    application.id
                )
                notification.persist()
            }

            Response.ok().build()
        } else {
            Response.status(422).entity("Application is not in state " + ApplicationStatus.STOPPED).build()
        }
    }

    private fun deploy(application: Application) {
        this.deploymentService.deploy(application)
        application.status = ApplicationStatus.RUNNING
        application.startedAt = LocalDateTime.now()
    }

    @PATCH
    @Path("/deny/{id}")
    @RolesAllowed("teacher")
    @Transactional
    fun denyApplication(@PathParam("id") id: Long?, message: DenyMessageDto?): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(404).build()

        return if (application.status == ApplicationStatus.PENDING) {
            application.status = ApplicationStatus.DENIED

            application.namespace.users.forEach {
                val notification = Notification(
                    it,
                    "Application has been denied!",
                    message?.message,
                    NotificationStatus.NEGATIVE,
                    "application",
                    application.id
                )
                notification.persist()
            }

            Response.ok().build()
        } else {
            Response.status(422).entity("Application is not in state " + ApplicationStatus.PENDING).build()
        }
    }

    @PATCH
    @Path("/stop/{id}")
    @RolesAllowed(value = ["teacher", "student"])
    @Transactional
    fun stopApplication(@PathParam("id") id: Long?, @Context ctx: SecurityContext): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(404).build()

        if (!ctx.isUserInRole("teacher") && application.owner.name != ctx.userPrincipal.name) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }

        return if (application.status == ApplicationStatus.RUNNING) {
            finishStopApplication(application, ApplicationStatus.STOPPED)
            Response.ok().build()
        } else {
            Response.status(422).entity("Application is not in state " + ApplicationStatus.RUNNING).build()
        }
    }

    @PATCH
    @Path("/finish/{id}")
    @RolesAllowed(value = ["teacher", "student"])
    @Transactional
    fun finishApplication(@PathParam("id") id: Long?, @Context ctx: SecurityContext): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(404).build()

        if (!ctx.isUserInRole("teacher") && application.owner.name != ctx.userPrincipal.name) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }

        return if (application.status == ApplicationStatus.RUNNING || application.status == ApplicationStatus.STOPPED) {
            finishStopApplication(application, ApplicationStatus.FINISHED)
            Response.ok().build()
        } else {
            Response.status(422)
                .entity("Application is not in state " + ApplicationStatus.RUNNING + " or " + ApplicationStatus.STOPPED)
                .build()
        }
    }

    @PATCH
    @Path("/request/{id}")
    @RolesAllowed(value = ["teacher", "student"])
    @Transactional
    fun requestApplication(@PathParam("id") id: Long?, @Context ctx: SecurityContext): Response? {
        val application = Application.findById<Application>(id)
            ?: return Response.status(404).build()

        if (!ctx.isUserInRole("teacher") && application.owner.name != ctx.userPrincipal.name) {
            return Response.status(Response.Status.FORBIDDEN).build();
        }

        return if (application.status == ApplicationStatus.DENIED) {
            application.status = ApplicationStatus.PENDING
            Response.ok().build()
        } else {
            Response.status(422).entity("Application is not in state " + ApplicationStatus.DENIED).build()
        }
    }

    private fun finishStopApplication(application: Application, status: ApplicationStatus) {
        deploymentService.stop(application)
        application.status = status
        if (status == ApplicationStatus.FINISHED) {
            application.finishedAt = LocalDateTime.now()
        }

        application.namespace.users.forEach {
            val notification = Notification(
                it,
                "Application has been ${status.toString().lowercase()}!",
                "",
                NotificationStatus.NEUTRAL,
                "application",
                application.id
            )
            notification.persist()
        }

        val isLastApplication = Application
            .streamAll<Application>()
            .filter {
                it.status == ApplicationStatus.RUNNING && it.namespace == application.namespace
            }.count() == 0L

        if (isLastApplication) {
            application.namespace.isDeleted = true
            namespaceService.deleteNamespace(application.namespace.namespace)
        }

        deploymentService.client.extensions().ingresses().withLabel("beeyond-application-id", application.id.toString())
            .delete()
    }
}