1
0
forked from SPC/spc-site
This commit is contained in:
Alexander Nozik 2022-05-02 09:49:18 +03:00
parent bafbf200f2
commit 4cef4ee402
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
14 changed files with 136 additions and 61 deletions

View File

@ -20,13 +20,15 @@ application {
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}
//tasks.withType<KotlinCompile>{
// kotlinOptions{
// freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers"
// }
//}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>{
kotlinOptions{
languageVersion = "1.7"
apiVersion = "1.7"
freeCompilerArgs = freeCompilerArgs + "-Xcontext-receivers"
}
}
val dataforgeVersion by extra("0.6.0-dev-3")
val dataforgeVersion by extra("0.6.0-dev-4")
val ktorVersion = KScienceVersions.ktorVersion
dependencies {

View File

@ -6,7 +6,7 @@ import io.ktor.server.application.call
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.html.respondHtml
import io.ktor.server.http.content.resources
import io.ktor.server.http.content.files
import io.ktor.server.http.content.static
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.statuspages.StatusPages
@ -14,43 +14,48 @@ import io.ktor.server.response.respond
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import kotlinx.css.CssBuilder
import kotlinx.html.CommonAttributeGroupFacade
import kotlinx.html.style
import ru.mipt.plugins.configureTemplating
import ru.mipt.spc.magprog.DataSetSiteContext
import ru.mipt.spc.magprog.SiteContext
import ru.mipt.spc.magprog.DataSetPageContext
import ru.mipt.spc.magprog.PageContext
import ru.mipt.spc.magprog.magProgPage
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.io
import space.kscience.dataforge.io.yaml.YamlPlugin
import space.kscience.snark.DirectoryDataTree
import space.kscience.snark.SnarkPlugin
import java.nio.file.Path
fun CommonAttributeGroupFacade.css(block: CssBuilder.() -> Unit) {
style = CssBuilder().block().toString()
}
class AuthenticationException : RuntimeException()
class AuthorizationException : RuntimeException()
internal fun Application.magProgSite(prefix: String = "magprog"){
val context = Context("spc-site"){
plugin(YamlPlugin)
internal fun Application.magProgPage(rootPath: Path, prefix: String = "magprog") {
val context = Context("spc-site") {
plugin(SnarkPlugin)
}
val io = context.io
val directory = javaClass.getResource("/magprog/content")!!.toURI()
val content = DirectoryDataTree(io, Path.of(directory))
val content = DirectoryDataTree(io, rootPath.resolve("content"))
val magprogSiteContext: SiteContext = DataSetSiteContext(context,"magprog", content)
val magprogPageContext: PageContext = DataSetPageContext(context, prefix, content)
routing {
route(prefix){
route(prefix) {
get {
call.respondHtml {
with(magprogSiteContext){
with(magprogPageContext) {
magProgPage()
}
}
}
static {
resources("magprog/assets")
files(rootPath.resolve("assets").toFile())
}
}
}
@ -59,12 +64,12 @@ internal fun Application.magProgSite(prefix: String = "magprog"){
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
magProgSite()
magProgPage(rootPath = Path.of(javaClass.getResource("/magprog")!!.toURI()))
install(StatusPages) {
exception<AuthenticationException> { call, cause ->
exception<AuthenticationException> { call, _ ->
call.respond(HttpStatusCode.Unauthorized)
}
exception<AuthorizationException> { call, cause ->
exception<AuthorizationException> { call, _ ->
call.respond(HttpStatusCode.Forbidden)
}
}

View File

@ -19,15 +19,16 @@ import space.kscience.dataforge.meta.toMeta
import space.kscience.dataforge.misc.DFInternal
import space.kscience.dataforge.names.Name
import space.kscience.snark.DirectoryDataTree.Companion.META_FILE_EXTENSION_KEY
import space.kscience.snark.HtmlData
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
class DataSetSiteContext(
class DataSetPageContext(
override val context: Context,
val prefix: String,
val dataSet: DataSet<Any>,
) : SiteContext {
) : PageContext {
override fun resolveResource(name: String): String = "$prefix/$name"

View File

@ -9,10 +9,11 @@ import space.kscience.dataforge.meta.string
import space.kscience.dataforge.misc.DFInternal
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
import space.kscience.snark.HtmlData
import kotlin.reflect.KType
import kotlin.reflect.typeOf
interface SiteContext: ContextAware {
interface PageContext: ContextAware {
/**
* Resolve a resource full path by its name
@ -37,12 +38,12 @@ interface SiteContext: ContextAware {
}
@OptIn(DFInternal::class)
inline fun <reified T: Any> SiteContext.resolve(name: Name): Data<T>? = resolve(typeOf<T>(), name)
inline fun <reified T: Any> PageContext.resolve(name: Name): Data<T>? = resolve(typeOf<T>(), name)
@OptIn(DFInternal::class)
inline fun <reified T:Any> SiteContext.resolveAll(noinline filter: (name: Name, meta: Meta) -> Boolean): DataSet<T> =
inline fun <reified T:Any> PageContext.resolveAll(noinline filter: (name: Name, meta: Meta) -> Boolean): DataSet<T> =
resolveAll(typeOf<T>(), filter)
fun SiteContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta ->
fun PageContext.findByType(contentType: String, baseName: Name = Name.EMPTY) = resolveAllHtml { name, meta ->
name.startsWith(baseName) && meta["content_type"].string == contentType
}

View File

@ -2,14 +2,19 @@ package ru.mipt.spc.magprog
import kotlinx.css.*
import kotlinx.html.*
import ru.mipt.css
import space.kscience.dataforge.meta.string
import space.kscience.snark.HtmlData
import space.kscience.snark.htmlData
import space.kscience.snark.id
import space.kscience.snark.order
class Person(val block: HtmlData) : HtmlData by block {
val name: String by meta.string { error("Mentor name is not defined") }
val photo: String? by meta.string()
}
context(SiteContext)
context(PageContext)
private fun FlowContent.personCards(list: List<Person>, prefix: String) {
list.forEach { mentor ->
section {
@ -34,7 +39,7 @@ private fun FlowContent.personCards(list: List<Person>, prefix: String) {
}
}
context(SiteContext)
context(PageContext)
fun FlowContent.mentors() {
val mentors = findByType("magprog_mentor").values.map {
Person(it)
@ -54,7 +59,7 @@ fun FlowContent.mentors() {
personCards(mentors,"mentor")
}
context(SiteContext)
context(PageContext)
fun FlowContent.team() {
val team = findByType("magprog_team").values.map { Person(it) }.sortedBy { it.order }

View File

@ -3,6 +3,7 @@ package ru.mipt.spc.magprog
import kotlinx.coroutines.runBlocking
import kotlinx.css.*
import kotlinx.html.*
import ru.mipt.css
import space.kscience.dataforge.data.await
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
@ -11,6 +12,9 @@ import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.snark.HtmlData
import space.kscience.snark.htmlData
import space.kscience.snark.id
//fun CssBuilder.magProgCss() {
// rule(".magprog-body") {
@ -66,13 +70,13 @@ private val PROGRAM_PATH: Name = CONTENT_NODE_NAME + "program"
private val RECOMMENDED_COURSES_PATH: Name = CONTENT_NODE_NAME + "recommendedCourses"
private val PARTNERS_PATH: Name = CONTENT_NODE_NAME + "partners"
context(SiteContext) private fun FlowContent.programSection() {
context(PageContext) private fun FlowContent.programSection() {
val programBlock = resolveHtml(PROGRAM_PATH)!!
val recommendedBlock = resolveHtml(RECOMMENDED_COURSES_PATH)!!
div("inner") {
h2 { +"Учебная программа" }
htmlData(programBlock)
button(classes = "fit btn btn-primary btn-lg") {
button(classes = "fit btn btn-secondary") {
attributes["data-bs-toggle"] = "collapse"
attributes["data-bs-target"] = "#recommended-courses-collapse-text"
attributes["aria-expanded"] = "false"
@ -81,14 +85,14 @@ context(SiteContext) private fun FlowContent.programSection() {
}
div("collapse pt-3") {
id = "recommended-courses-collapse-text"
div("card card-body") {
div {
htmlData(recommendedBlock)
}
}
}
}
context(SiteContext) private fun FlowContent.partners() {
context(PageContext) private fun FlowContent.partners() {
//val partnersData: Meta = resolve<Any>(PARTNERS_PATH)?.meta ?: Meta.EMPTY
val partnersData: Meta = runBlocking { resolve<Meta>(PARTNERS_PATH)?.await()} ?: Meta.EMPTY
div("inner") {
@ -113,7 +117,7 @@ context(SiteContext) private fun FlowContent.partners() {
}
}
context(SiteContext) fun HTML.magProgPage() {
context(PageContext) fun HTML.magProgPage() {
val sections = listOf<MagProgSection>(
wrapSection(resolveHtml(INTRO_PATH)!!, "intro"),
MagProgSection(
@ -158,6 +162,10 @@ context(SiteContext) fun HTML.magProgPage() {
name = "viewport"
content = "width=device-width, initial-scale=1, user-scalable=no"
}
link {
rel = "stylesheet"
href = resolveResource("css/bootstrap.min.css")
}
link {
rel = "stylesheet"
href = resolveResource("css/main.css")
@ -237,6 +245,9 @@ context(SiteContext) fun HTML.magProgPage() {
script {
src = resolveResource("js/util.js")
}
script {
src = resolveResource("js/bootstrap.min.js")
}
script {
src = resolveResource("js/main.js")
}

View File

@ -1,9 +0,0 @@
package ru.mipt.spc.magprog
import kotlinx.css.CssBuilder
import kotlinx.html.CommonAttributeGroupFacade
import kotlinx.html.style
fun CommonAttributeGroupFacade.css(block: CssBuilder.() -> Unit) {
style = CssBuilder().block().toString()
}

View File

@ -13,10 +13,8 @@ import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.nameWithoutExtension
import java.nio.file.attribute.BasicFileAttributes
import kotlin.io.path.*
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@ -32,7 +30,10 @@ class DirectoryDataTree(val io: IOPlugin, val path: Path) : DataTree<ByteArray>
val meta = envelope.meta.copy {
META_FILE_PATH_KEY put filePath.toString()
META_FILE_EXTENSION_KEY put filePath.extension
//TODO add other file information
val attributes = filePath.readAttributes<BasicFileAttributes>()
META_FILE_UPDATE_TIME_KEY put attributes.lastModifiedTime().toInstant().toString()
META_FILE_CREATE_TIME_KEY put attributes.creationTime().toInstant().toString()
}
return Data(meta){
envelope.data?.toByteArray() ?: ByteArray(0)
@ -57,6 +58,8 @@ class DirectoryDataTree(val io: IOPlugin, val path: Path) : DataTree<ByteArray>
val META_FILE_KEY = "file".asName()
val META_FILE_PATH_KEY = META_FILE_KEY + "path"
val META_FILE_EXTENSION_KEY = META_FILE_KEY + "extension"
val META_FILE_CREATE_TIME_KEY = META_FILE_KEY +"created"
val META_FILE_UPDATE_TIME_KEY = META_FILE_KEY +"update"
}
}

View File

@ -1,4 +1,4 @@
package ru.mipt.spc.magprog
package space.kscience.snark
import kotlinx.coroutines.runBlocking
import kotlinx.html.FlowContent

View File

@ -0,0 +1,20 @@
package space.kscience.snark
import io.ktor.http.ContentType
import kotlinx.html.div
import kotlinx.html.unsafe
import space.kscience.dataforge.meta.Meta
import kotlin.reflect.KType
import kotlin.reflect.typeOf
object SnarkHtmlParser:SnarkParser<HtmlFragment> {
override val contentType: ContentType = ContentType.Text.Html
override val fileExtensions: Set<String> = setOf("html")
override val resultType: KType = typeOf<HtmlFragment>()
override suspend fun parse(bytes: ByteArray, meta: Meta): HtmlFragment = {
div{
unsafe { +bytes.decodeToString() }
}
}
}

View File

@ -8,8 +8,10 @@ import space.kscience.dataforge.io.yaml.YamlPlugin
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.reflect.KClass
import kotlin.reflect.KType
@ -19,18 +21,20 @@ interface SnarkParser<R : Any> {
val fileExtensions: Set<String>
val priority: Int
val priority: Int get() = DEFAULT_PRIORITY
val resultType: KType
suspend fun parse(bytes: ByteArray): R
suspend fun parse(bytes: ByteArray, meta: Meta): R
companion object {
const val TYPE = "snark.parser"
const val DEFAULT_PRIORITY = 10
}
}
@OptIn(DFExperimental::class)
class SnarkPlugin : AbstractPlugin() {
val yaml by require(YamlPlugin)
val io get() = yaml.io
@ -40,13 +44,30 @@ class SnarkPlugin : AbstractPlugin() {
private val parsers: Map<Name, SnarkParser<*>> by lazy { context.gather(SnarkParser.TYPE, true) }
private val parseAction = Action.map<ByteArray, Any> {
result { bytes ->
parsers.values.filter { parser ->
parser.contentType.toString() == meta["contentType"].string ||
meta[DirectoryDataTree.META_FILE_EXTENSION_KEY].string in parser.fileExtensions
}.maxByOrNull {
it.priority
}?.parse(bytes) ?: bytes
val parser: SnarkParser<*>? = parsers.values.filter { parser ->
parser.contentType.toString() == meta["contentType"].string ||
meta[DirectoryDataTree.META_FILE_EXTENSION_KEY].string in parser.fileExtensions
}.maxByOrNull {
it.priority
}
//ensure that final type is correct
if (parser == null) {
logger.warn { "The parser is not found for data with meta $meta" }
result { it }
} else {
result(parser.resultType) { bytes ->
parser.parse(bytes, meta)
}
}
}
override fun content(target: String): Map<Name, Any> {
return when(target){
SnarkParser.TYPE -> mapOf(
"html".asName() to SnarkHtmlParser
)
else ->super.content(target)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,7 @@ package ru.mipt
import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.testApplication
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
@ -10,7 +11,7 @@ class ApplicationTest {
@Test
fun testRoot() = testApplication {
application {
magProgSite()
magProgPage(rootPath = Path.of(javaClass.getResource("/magprog")!!.toURI()))
}
client.get("/magprog").apply {
assertEquals(HttpStatusCode.OK, status)