Compare commits
	
		
			2 Commits
		
	
	
		
			017b235f2c
			...
			dev
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					760589a0ee | ||
| 
						 | 
					36d8a1dd32 | 
							
								
								
									
										33
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					# Use a JDK base image for building
 | 
				
			||||||
 | 
					FROM gradle:8.10.2-jdk17-alpine AS builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set working directory
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy build files
 | 
				
			||||||
 | 
					COPY build.gradle.kts settings.gradle.kts ./
 | 
				
			||||||
 | 
					COPY src ./src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build the application
 | 
				
			||||||
 | 
					RUN gradle build #--no-daemon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Use a lightweight JRE image for running
 | 
				
			||||||
 | 
					FROM eclipse-temurin:17-alpine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set working directory
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Install Chromium and ChromeDriver
 | 
				
			||||||
 | 
					RUN apk add --no-cache chromium chromium-chromedriver nss
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV CHROME_BIN=/usr/bin/chromium-browser
 | 
				
			||||||
 | 
					ENV CHROMEDRIVER_BIN=/usr/bin/chromedriver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy the built JAR from the builder stage
 | 
				
			||||||
 | 
					COPY --from=builder /app/build/libs/*.jar app.jar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Expose the port (default for Spring Boot)
 | 
				
			||||||
 | 
					EXPOSE 8080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Run the application
 | 
				
			||||||
 | 
					ENTRYPOINT ["java", "-jar", "app.jar"]
 | 
				
			||||||
@@ -43,6 +43,10 @@ dependencies {
 | 
				
			|||||||
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
 | 
					    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
 | 
				
			||||||
    implementation("org.seleniumhq.selenium:selenium-java:4.35.0")
 | 
					    implementation("org.seleniumhq.selenium:selenium-java:4.35.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation("com.squareup.okhttp3:okhttp:4.12.0")
 | 
				
			||||||
 | 
					    implementation("com.google.code.gson:gson:2.10.1")
 | 
				
			||||||
 | 
					    implementation("org.jsoup:jsoup:1.17.2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    testImplementation("org.springframework.security:spring-security-test")
 | 
					    testImplementation("org.springframework.security:spring-security-test")
 | 
				
			||||||
	testImplementation("org.springframework.boot:spring-boot-starter-test")
 | 
						testImplementation("org.springframework.boot:spring-boot-starter-test")
 | 
				
			||||||
	testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
 | 
						testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,10 @@ package net.xintanalabs.rssotto
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
 | 
					import org.springframework.boot.autoconfigure.SpringBootApplication
 | 
				
			||||||
import org.springframework.boot.runApplication
 | 
					import org.springframework.boot.runApplication
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.EnableScheduling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootApplication
 | 
					@SpringBootApplication
 | 
				
			||||||
 | 
					@EnableScheduling
 | 
				
			||||||
class RssottoApplication
 | 
					class RssottoApplication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun main(args: Array<String>) {
 | 
					fun main(args: Array<String>) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,22 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.components.checkers
 | 
					package net.xintanalabs.rssotto.components.checkers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import net.xintanalabs.rssotto.components.checker.api.ApiChecker
 | 
					import net.xintanalabs.rssotto.components.checker.api.ApiChecker
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checker.manual.ManualChecker
 | 
				
			||||||
import net.xintanalabs.rssotto.components.checkers.scrape.ScrapeChecker
 | 
					import net.xintanalabs.rssotto.components.checkers.scrape.ScrapeChecker
 | 
				
			||||||
import org.springframework.stereotype.Component
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
class CheckerFactory(
 | 
					class CheckerFactory(
 | 
				
			||||||
    private val apiChecker: ApiChecker,
 | 
					    private val apiChecker: ApiChecker,
 | 
				
			||||||
    private val scrapeChecker: ScrapeChecker
 | 
					    private val scrapeChecker: ScrapeChecker,
 | 
				
			||||||
 | 
					    private val manualChecker: ManualChecker
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    fun createChecker(type: String): IVersionChecker {
 | 
					    fun createChecker(type: String): IVersionChecker {
 | 
				
			||||||
        val parts = type.split(":")
 | 
					        val parts = type.split(":")
 | 
				
			||||||
        return when (parts[0].lowercase()) {
 | 
					        return when (parts[0].lowercase()) {
 | 
				
			||||||
            "scrape" -> scrapeChecker
 | 
					            "scrape" -> scrapeChecker
 | 
				
			||||||
            "api" -> apiChecker
 | 
					            "api" -> apiChecker
 | 
				
			||||||
 | 
					            "manual" -> manualChecker
 | 
				
			||||||
            else -> throw IllegalArgumentException("Unknown checker type: $type")
 | 
					            else -> throw IllegalArgumentException("Unknown checker type: $type")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.components.checkers
 | 
					package net.xintanalabs.rssotto.components.checkers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import net.xintanalabs.rssotto.model.Source
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IVersionChecker {
 | 
					interface IVersionChecker {
 | 
				
			||||||
    suspend fun getLatestVersion(paramsDict: Map<String, String>): String
 | 
					    suspend fun getLatestVersion(paramsDict: Map<String, String>): String?
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -10,7 +10,7 @@ import kotlin.text.get
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
class ApiChecker(private val webClient: WebClient) : IVersionChecker {
 | 
					class ApiChecker(private val webClient: WebClient) : IVersionChecker {
 | 
				
			||||||
    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String {
 | 
					    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String? {
 | 
				
			||||||
       val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
					       val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
				
			||||||
            ?: throw IllegalArgumentException("API URL required")
 | 
					            ?: throw IllegalArgumentException("API URL required")
 | 
				
			||||||
        val jsonPath = paramsDict["jsonPath"]?.takeIf { it.isNotEmpty() }
 | 
					        val jsonPath = paramsDict["jsonPath"]?.takeIf { it.isNotEmpty() }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.components.checker.exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CheckerException(
 | 
				
			||||||
 | 
					    message: String?,
 | 
				
			||||||
 | 
					    val info: Map<String, String>? = null,
 | 
				
			||||||
 | 
					    cause: Throwable? = null,
 | 
				
			||||||
 | 
					) : Exception(message, cause)
 | 
				
			||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.components.checker.exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScraperFetcherException(
 | 
				
			||||||
 | 
					    message: String,
 | 
				
			||||||
 | 
					    status: Int? = null,
 | 
				
			||||||
 | 
					    cause: Throwable? = null
 | 
				
			||||||
 | 
					) : Exception(message, cause)
 | 
				
			||||||
@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.components.checker.manual
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checkers.IVersionChecker
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.AppService
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					class ManualChecker(
 | 
				
			||||||
 | 
					    private val appService: AppService
 | 
				
			||||||
 | 
					): IVersionChecker {
 | 
				
			||||||
 | 
					    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String? {
 | 
				
			||||||
 | 
					        val id = paramsDict["id"] ?: return ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val app = appService.getAppById(id)
 | 
				
			||||||
 | 
					        return app?.latestVersion
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.components.checker.scrape
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gson.Gson
 | 
				
			||||||
 | 
					import com.google.gson.JsonObject
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checkers.scrape.IScrapeFetcher
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
 | 
					import okhttp3.*
 | 
				
			||||||
 | 
					import okhttp3.MediaType.Companion.toMediaType
 | 
				
			||||||
 | 
					import okhttp3.RequestBody.Companion.toRequestBody
 | 
				
			||||||
 | 
					import java.io.IOException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component(value="flaresolverr")
 | 
				
			||||||
 | 
					class FlaresSolverrFetcher: IScrapeFetcher {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        private val client = OkHttpClient.Builder()
 | 
				
			||||||
 | 
					            .connectTimeout(60, java.util.concurrent.TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .readTimeout(120, java.util.concurrent.TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .writeTimeout(120, java.util.concurrent.TimeUnit.SECONDS)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun fetch(url: String): String {
 | 
				
			||||||
 | 
					        val flaresolverUrl = "http://192.168.1.112:8191/v1"
 | 
				
			||||||
 | 
					        val gson = Gson()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val createSessionData = mapOf("cmd" to "sessions.create")
 | 
				
			||||||
 | 
					        val createJson = gson.toJson(createSessionData)
 | 
				
			||||||
 | 
					        val createBody = createJson.toRequestBody("application/json".toMediaType())
 | 
				
			||||||
 | 
					        val createRequest = Request.Builder()
 | 
				
			||||||
 | 
					            .url(flaresolverUrl)
 | 
				
			||||||
 | 
					            .post(createBody)
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            val createResponse = client.newCall(createRequest).execute()
 | 
				
			||||||
 | 
					            if (!createResponse.isSuccessful) {
 | 
				
			||||||
 | 
					                throw IOException("Unexpected code ${createResponse.code}")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            val jsonResponse = gson.fromJson(createResponse.body?.string(), JsonObject::class.java)
 | 
				
			||||||
 | 
					            val sessionId = jsonResponse.get("session")?.asString
 | 
				
			||||||
 | 
					                ?: throw IOException("No session ID received")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val scrapeData = mapOf(
 | 
				
			||||||
 | 
					                "cmd" to "request.get",
 | 
				
			||||||
 | 
					                "url" to url,
 | 
				
			||||||
 | 
					                "maxTimeout" to 60000,
 | 
				
			||||||
 | 
					                "session" to sessionId
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            val scrapeJson = gson.toJson(scrapeData)
 | 
				
			||||||
 | 
					            val scrapeBody = scrapeJson.toRequestBody("application/json".toMediaType())
 | 
				
			||||||
 | 
					            val scrapeRequest = Request.Builder()
 | 
				
			||||||
 | 
					                .url(flaresolverUrl)
 | 
				
			||||||
 | 
					                .post(scrapeBody)
 | 
				
			||||||
 | 
					                .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val scrapeResponse = client.newCall(scrapeRequest).execute()
 | 
				
			||||||
 | 
					            if (!scrapeResponse.isSuccessful) {
 | 
				
			||||||
 | 
					                throw IOException("Failed to scrape: ${scrapeResponse.code}")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            val scrapeJsonResponse = gson.fromJson(scrapeResponse.body?.string(), JsonObject::class.java)
 | 
				
			||||||
 | 
					            val html = scrapeJsonResponse.getAsJsonObject("solution")?.get("response")?.asString
 | 
				
			||||||
 | 
					                ?: throw IOException("No HTML received")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return html
 | 
				
			||||||
 | 
					        } catch (e: IOException) {
 | 
				
			||||||
 | 
					            throw RuntimeException("Error during scraping: ${e.message}", e)
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            client.dispatcher.executorService.shutdown()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.components.checkers.scrape
 | 
					package net.xintanalabs.rssotto.components.checkers.scrape
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.coroutines.delay
 | 
					import kotlinx.coroutines.delay
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checker.exceptions.ScraperFetcherException
 | 
				
			||||||
 | 
					import org.jsoup.HttpStatusException
 | 
				
			||||||
import org.jsoup.Jsoup
 | 
					import org.jsoup.Jsoup
 | 
				
			||||||
import org.springframework.stereotype.Component
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,6 +20,8 @@ class JSoupFetcher: IScrapeFetcher {
 | 
				
			|||||||
                .header("Upgrade-Insecure-Requests", "1")
 | 
					                .header("Upgrade-Insecure-Requests", "1")
 | 
				
			||||||
                .get()
 | 
					                .get()
 | 
				
			||||||
                .html()
 | 
					                .html()
 | 
				
			||||||
 | 
					        } catch (httpe: HttpStatusException) {
 | 
				
			||||||
 | 
					            throw ScraperFetcherException("HTTP error fetching ${url}: ${httpe.statusCode}", httpe.statusCode, httpe)
 | 
				
			||||||
        } catch (e: Exception) {
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
            throw RuntimeException("Error fetching ${url}", e)
 | 
					            throw RuntimeException("Error fetching ${url}", e)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.components.checkers.scrape
 | 
					package net.xintanalabs.rssotto.components.checkers.scrape
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.coroutines.delay
 | 
					import net.xintanalabs.rssotto.components.checker.exceptions.CheckerException
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checker.scrape.FlaresSolverrFetcher
 | 
				
			||||||
import net.xintanalabs.rssotto.components.checkers.IVersionChecker
 | 
					import net.xintanalabs.rssotto.components.checkers.IVersionChecker
 | 
				
			||||||
import net.xintanalabs.rssotto.model.Source
 | 
					import net.xintanalabs.rssotto.model.SiteCache
 | 
				
			||||||
import net.xintanalabs.rssotto.tasks.ScheduledTasks
 | 
					import net.xintanalabs.rssotto.service.SiteCacheService
 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeDriver
 | 
					import org.openqa.selenium.chrome.ChromeDriver
 | 
				
			||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import org.springframework.stereotype.Component
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
@@ -13,31 +14,82 @@ import java.util.regex.Pattern
 | 
				
			|||||||
@Component
 | 
					@Component
 | 
				
			||||||
class ScrapeChecker(
 | 
					class ScrapeChecker(
 | 
				
			||||||
    private val restTemplate: RestTemplate,
 | 
					    private val restTemplate: RestTemplate,
 | 
				
			||||||
    private val chromeDriver: ChromeDriver
 | 
					    private val chromeDriver: ChromeDriver,
 | 
				
			||||||
 | 
					    private val siteCacheService: SiteCacheService
 | 
				
			||||||
) : IVersionChecker {
 | 
					) : IVersionChecker {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val log = LoggerFactory.getLogger(ScrapeChecker::class.java)
 | 
					    private val log = LoggerFactory.getLogger(ScrapeChecker::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String {
 | 
					    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String? {
 | 
				
			||||||
        val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
					        val info = mutableMapOf<String, String>()
 | 
				
			||||||
            ?: throw IllegalArgumentException("URL required")
 | 
					        return try {
 | 
				
			||||||
        log.info("Url : {}", url)
 | 
					            val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
				
			||||||
        val mode = paramsDict["mode"]
 | 
					                ?: throw IllegalArgumentException("URL required")
 | 
				
			||||||
        log.info("Mode : {}", mode)
 | 
					            info["url"] = url
 | 
				
			||||||
        val fetcher: IScrapeFetcher = when (mode) {
 | 
					            val mode = paramsDict["mode"]
 | 
				
			||||||
            "selenium" -> SeleniumFetcher(chromeDriver)
 | 
					            val cached: Boolean = paramsDict["cached"]?.toBoolean() ?: false
 | 
				
			||||||
            "jsoup" -> JSoupFetcher()
 | 
					            info["mode"] = mode ?: ""
 | 
				
			||||||
            else -> DefaultScrapeFetcher(restTemplate)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        val response = fetcher.fetch(url)
 | 
					 | 
				
			||||||
        val cleanedResponse = response.replace(">\\s+<".toRegex(), "><")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val regex: String = paramsDict["regex"] as String
 | 
					            val fetcher: IScrapeFetcher = when (mode) {
 | 
				
			||||||
        log.info("Regex : {}", regex)
 | 
					                "selenium" -> SeleniumFetcher(chromeDriver)
 | 
				
			||||||
        val match = Pattern.compile(regex).matcher(cleanedResponse)
 | 
					                "jsoup" -> JSoupFetcher()
 | 
				
			||||||
        if (!match.find() || match.groupCount() < 1) {
 | 
					                "flaresolverr" -> FlaresSolverrFetcher()
 | 
				
			||||||
            throw Exception("No match with regex in response")
 | 
					                else -> DefaultScrapeFetcher(restTemplate)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var response = ""
 | 
				
			||||||
 | 
					            var hasCached = false
 | 
				
			||||||
 | 
					            if (cached) {
 | 
				
			||||||
 | 
					                val developerId = paramsDict["developerId"] ?: url
 | 
				
			||||||
 | 
					                val siteCache = siteCacheService.getCacheByDeveloperId(developerId)
 | 
				
			||||||
 | 
					                if (siteCache != null) {
 | 
				
			||||||
 | 
					                    if (siteCacheService.isCacheValid(siteCache)) {
 | 
				
			||||||
 | 
					                        response = siteCache.html
 | 
				
			||||||
 | 
					                        hasCached = true
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        siteCacheService.deleteById(siteCache.id ?: "")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!hasCached) {
 | 
				
			||||||
 | 
					                response = cleanHtml(fetcher.fetch(url)).trim()
 | 
				
			||||||
 | 
					                if (cached) {
 | 
				
			||||||
 | 
					                    val developerId = paramsDict["developerId"] ?: url
 | 
				
			||||||
 | 
					                    siteCacheService.create(
 | 
				
			||||||
 | 
					                        SiteCache(
 | 
				
			||||||
 | 
					                            developerId = developerId,
 | 
				
			||||||
 | 
					                            html = response,
 | 
				
			||||||
 | 
					                            createdAt = System.currentTimeMillis(),
 | 
				
			||||||
 | 
					                            expiresAt = System.currentTimeMillis() + (3600_000 * 5) // 5 hour
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (response.isEmpty()) {
 | 
				
			||||||
 | 
					                throw Exception("Empty response from URL")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //val cleanedResponse = response.replace(">\\s+<".toRegex(), "><")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val regex: String = paramsDict["regex"] as String
 | 
				
			||||||
 | 
					            info["regex"] = regex
 | 
				
			||||||
 | 
					            val match = Pattern.compile(regex).matcher(response)
 | 
				
			||||||
 | 
					            if (!match.find() || match.groupCount() < 1) {
 | 
				
			||||||
 | 
					                throw Exception("No match with regex in response")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            match.group(1)
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            log.error("Error in ScrapeChecker: ${e.message}")
 | 
				
			||||||
 | 
					            throw CheckerException(e.message, info, e)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return match.group(1)
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun cleanHtml(html: String): String {
 | 
				
			||||||
 | 
					        return html.replace(Regex("<script.*?</script>"), "")
 | 
				
			||||||
 | 
					            .replace(Regex("<style.*?</style>"), "")
 | 
				
			||||||
 | 
					            .replace(Regex("<!--.*?-->"), "")
 | 
				
			||||||
 | 
					            .replace(Regex("\\s+"), " ")
 | 
				
			||||||
 | 
					            .trim()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.boot.context.properties.ConfigurationProperties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@ConfigurationProperties("rssotto")
 | 
				
			||||||
 | 
					data class RssottoProperties (
 | 
				
			||||||
 | 
					    val checkTaskCron: String = "*/45 * * * *"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -9,12 +9,27 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 | 
				
			|||||||
import org.springframework.security.config.http.SessionCreationPolicy
 | 
					import org.springframework.security.config.http.SessionCreationPolicy
 | 
				
			||||||
import org.springframework.security.web.DefaultSecurityFilterChain
 | 
					import org.springframework.security.web.DefaultSecurityFilterChain
 | 
				
			||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
 | 
					import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
 | 
				
			||||||
 | 
					import org.springframework.web.cors.CorsConfiguration
 | 
				
			||||||
 | 
					import org.springframework.web.cors.UrlBasedCorsConfigurationSource
 | 
				
			||||||
 | 
					import org.springframework.web.filter.CorsFilter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Configuration
 | 
					@Configuration
 | 
				
			||||||
@EnableWebSecurity
 | 
					@EnableWebSecurity
 | 
				
			||||||
class SecurityConfiguration(
 | 
					class SecurityConfiguration(
 | 
				
			||||||
    private val authenticationProvider: AuthenticationProvider
 | 
					    private val authenticationProvider: AuthenticationProvider
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    fun corsConfigurationSource(): UrlBasedCorsConfigurationSource {
 | 
				
			||||||
 | 
					        val config = CorsConfiguration()
 | 
				
			||||||
 | 
					        config.allowedOrigins = listOf("http://localhost:4200")
 | 
				
			||||||
 | 
					        config.allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS")
 | 
				
			||||||
 | 
					        config.allowedHeaders = listOf("*")
 | 
				
			||||||
 | 
					        config.allowCredentials = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val source = UrlBasedCorsConfigurationSource()
 | 
				
			||||||
 | 
					        source.registerCorsConfiguration("/**", config)
 | 
				
			||||||
 | 
					        return source
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Bean
 | 
					    @Bean
 | 
				
			||||||
    fun securityFilterChain(
 | 
					    fun securityFilterChain(
 | 
				
			||||||
@@ -22,10 +37,11 @@ class SecurityConfiguration(
 | 
				
			|||||||
        jwtAuthenticationFilter: JwtAuthenticationFilter
 | 
					        jwtAuthenticationFilter: JwtAuthenticationFilter
 | 
				
			||||||
    ): DefaultSecurityFilterChain =
 | 
					    ): DefaultSecurityFilterChain =
 | 
				
			||||||
        http
 | 
					        http
 | 
				
			||||||
 | 
					            .cors { }
 | 
				
			||||||
            .csrf { it.disable() }
 | 
					            .csrf { it.disable() }
 | 
				
			||||||
            .authorizeHttpRequests {
 | 
					            .authorizeHttpRequests {
 | 
				
			||||||
                it
 | 
					                it
 | 
				
			||||||
                    .requestMatchers("/api/auth", "/api/auth/refresh", "/error")
 | 
					                    .requestMatchers("/api/auth/**", "/error")
 | 
				
			||||||
                    .permitAll()
 | 
					                    .permitAll()
 | 
				
			||||||
                    .requestMatchers(HttpMethod.POST, "/api/user")
 | 
					                    .requestMatchers(HttpMethod.POST, "/api/user")
 | 
				
			||||||
                    .permitAll()
 | 
					                    .permitAll()
 | 
				
			||||||
@@ -40,4 +56,5 @@ class SecurityConfiguration(
 | 
				
			|||||||
            .authenticationProvider(authenticationProvider)
 | 
					            .authenticationProvider(authenticationProvider)
 | 
				
			||||||
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
 | 
					            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
 | 
				
			||||||
            .build()
 | 
					            .build()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,4 +6,7 @@ object Constants {
 | 
				
			|||||||
    const val COLLECTION_APPS: String = "apps"
 | 
					    const val COLLECTION_APPS: String = "apps"
 | 
				
			||||||
    const val COLLECTION_SOURCES: String = "sources"
 | 
					    const val COLLECTION_SOURCES: String = "sources"
 | 
				
			||||||
    const val COLLECTION_CHECKER_TYPES: String = "checkerTypes"
 | 
					    const val COLLECTION_CHECKER_TYPES: String = "checkerTypes"
 | 
				
			||||||
 | 
					    const val COLLECTION_NOTIFICATIONS: String = "notifications"
 | 
				
			||||||
 | 
					    const val COLLECTION_DEVELOPERS: String = "developers"
 | 
				
			||||||
 | 
					    const val COLLECTION_SITE_CACHE: String = "siteCache"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.controller.app.AppVersionResponse
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.ActionsService
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestParam
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("${Constants.API_BASE_PATH}/exec")
 | 
				
			||||||
 | 
					class ActionsController(
 | 
				
			||||||
 | 
					    private val actionsService: ActionsService
 | 
				
			||||||
 | 
					){
 | 
				
			||||||
 | 
					    @PostMapping("/check")
 | 
				
			||||||
 | 
					    fun checkApps(@RequestParam(name = "id") idList: List<String>): List<AppVersionResponse> {
 | 
				
			||||||
 | 
					        return actionsService.checkVersions(idList)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @PostMapping("/check-all")
 | 
				
			||||||
 | 
					    fun checkAllApps(): List<AppVersionResponse> {
 | 
				
			||||||
 | 
					        return actionsService.checkAllVersions()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.ModType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.App
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.AppService
 | 
				
			||||||
 | 
					import org.springframework.http.HttpStatus
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.GetMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PathVariable
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestBody
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestParam
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					import org.springframework.web.server.ResponseStatusException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("${Constants.API_BASE_PATH}/app")
 | 
				
			||||||
 | 
					class AppController(
 | 
				
			||||||
 | 
					    private val appService: AppService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @PostMapping
 | 
				
			||||||
 | 
					    fun createApp(@RequestBody app: AppRequest): AppResponse? =
 | 
				
			||||||
 | 
					        appService.createApp(app.toModel())
 | 
				
			||||||
 | 
					            ?.toResponse()
 | 
				
			||||||
 | 
					            ?:  throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create app")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/{id}")
 | 
				
			||||||
 | 
					    fun getApp(@PathVariable id: String): AppResponse? =
 | 
				
			||||||
 | 
					         appService.getAppById(id)
 | 
				
			||||||
 | 
					            ?.toResponse()
 | 
				
			||||||
 | 
					            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "App not found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping
 | 
				
			||||||
 | 
					    fun  getAllApps(): List<AppResponse> =
 | 
				
			||||||
 | 
					        appService.getAllApps()
 | 
				
			||||||
 | 
					            .map { it.toResponse() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/search")
 | 
				
			||||||
 | 
					    fun searchApps(@RequestParam q: String?, @RequestParam id: List<String>?): List<AppResponse> {
 | 
				
			||||||
 | 
					        if (id != null && id.isNotEmpty()) {
 | 
				
			||||||
 | 
					            return appService.getAppsByIds(id).map { it.toResponse() }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!q.isNullOrBlank()) {
 | 
				
			||||||
 | 
					            return appService.searchApps(q).map { it.toResponse() }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return emptyList()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun App.toVersionResponse(): AppVersionResponse {
 | 
				
			||||||
 | 
					        return AppVersionResponse(
 | 
				
			||||||
 | 
					            id = this.id,
 | 
				
			||||||
 | 
					            latestVersion = this.latestVersion,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun App.toResponse(): AppResponse {
 | 
				
			||||||
 | 
					        return AppResponse(
 | 
				
			||||||
 | 
					            id = this.id,
 | 
				
			||||||
 | 
					            name = this.name,
 | 
				
			||||||
 | 
					            type = this.type,
 | 
				
			||||||
 | 
					            source = this.source,
 | 
				
			||||||
 | 
					            params = this.params,
 | 
				
			||||||
 | 
					            fields = this.fields,
 | 
				
			||||||
 | 
					            downloadUrl = this.downloadUrl,
 | 
				
			||||||
 | 
					            latestVersion = this.latestVersion,
 | 
				
			||||||
 | 
					            createdAt = this.createdAt,
 | 
				
			||||||
 | 
					            updatedAt = this.updatedAt,
 | 
				
			||||||
 | 
					            lastCheckedAt = this.lastCheckedAt,
 | 
				
			||||||
 | 
					            active = this.active,
 | 
				
			||||||
 | 
					            developer = this.developer,
 | 
				
			||||||
 | 
					            modType = this.modType ?: ModType.NOT_SET
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun AppRequest.toModel(): App {
 | 
				
			||||||
 | 
					        return App(
 | 
				
			||||||
 | 
					            name = this.name,
 | 
				
			||||||
 | 
					            type = this.type,
 | 
				
			||||||
 | 
					            source = this.source,
 | 
				
			||||||
 | 
					            params = this.params,
 | 
				
			||||||
 | 
					            fields = this.fields,
 | 
				
			||||||
 | 
					            downloadUrl =  this.downloadUrl,
 | 
				
			||||||
 | 
					            latestVersion = this.latestVersion,
 | 
				
			||||||
 | 
					            createdAt = System.currentTimeMillis(),
 | 
				
			||||||
 | 
					            updatedAt = System.currentTimeMillis(),
 | 
				
			||||||
 | 
					            lastCheckedAt = 0,
 | 
				
			||||||
 | 
					            active = true,
 | 
				
			||||||
 | 
					            developer = this.developer,
 | 
				
			||||||
 | 
					            modType = this.modType
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.ModType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class AppRequest(
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val type: String = "",
 | 
				
			||||||
 | 
					    val source: String,
 | 
				
			||||||
 | 
					    val params: Map<String, String>,
 | 
				
			||||||
 | 
					    val fields: Map<String, String>,
 | 
				
			||||||
 | 
					    val downloadUrl: String,
 | 
				
			||||||
 | 
					    val latestVersion: String? = null,
 | 
				
			||||||
 | 
					    val createdAt: Long = 0,
 | 
				
			||||||
 | 
					    val updatedAt: Long = 0,
 | 
				
			||||||
 | 
					    val lastCheckedAt: Long = 0,
 | 
				
			||||||
 | 
					    val active: Boolean = true,
 | 
				
			||||||
 | 
					    val developer: String? = null,
 | 
				
			||||||
 | 
					    val modType: ModType
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.ModType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class AppResponse(
 | 
				
			||||||
 | 
					    val id: String? = null,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val type: String = "",
 | 
				
			||||||
 | 
					    val source: String,
 | 
				
			||||||
 | 
					    val params: Map<String, String>,
 | 
				
			||||||
 | 
					    val fields: Map<String, String>,
 | 
				
			||||||
 | 
					    val downloadUrl: String,
 | 
				
			||||||
 | 
					    val latestVersion: String? = null,
 | 
				
			||||||
 | 
					    val createdAt: Long = 0,
 | 
				
			||||||
 | 
					    val updatedAt: Long = 0,
 | 
				
			||||||
 | 
					    val lastCheckedAt: Long = 0,
 | 
				
			||||||
 | 
					    val active: Boolean = true,
 | 
				
			||||||
 | 
					    val developer: String? = null,
 | 
				
			||||||
 | 
					    val modType: ModType
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.data.annotation.Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class AppVersionResponse(
 | 
				
			||||||
 | 
					    @Id val id: String? = null,
 | 
				
			||||||
 | 
					    val latestVersion: String? = null
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.checkerType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.CheckerType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.CheckerTypeService
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.GetMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PathVariable
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestBody
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("${Constants.API_BASE_PATH}/checker-type")
 | 
				
			||||||
 | 
					class CheckerTypeController(
 | 
				
			||||||
 | 
					    private val checkerTypeService: CheckerTypeService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @PostMapping
 | 
				
			||||||
 | 
					    fun createCheckerType(@RequestBody checkerType: CheckerTypeRequest): CheckerTypeResponse {
 | 
				
			||||||
 | 
					        return checkerTypeService.create(checkerType.toModel()).toResponse()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping
 | 
				
			||||||
 | 
					    fun getAllCheckerTypes(): List<CheckerTypeResponse> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return checkerTypeService.findAll().map { it.toResponse() }
 | 
				
			||||||
 | 
					        } catch (e: InterruptedException) {
 | 
				
			||||||
 | 
					           throw e
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/{id}")
 | 
				
			||||||
 | 
					    fun getType(@PathVariable id: String): CheckerTypeResponse? {
 | 
				
			||||||
 | 
					        return checkerTypeService.findById(id).toResponse()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun CheckerType?.toResponse(): CheckerTypeResponse =
 | 
				
			||||||
 | 
					        CheckerTypeResponse(
 | 
				
			||||||
 | 
					            id = this?.id,
 | 
				
			||||||
 | 
					            name = this?.name ?: "",
 | 
				
			||||||
 | 
					            params = this?.params ?: listOf()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun CheckerTypeRequest.toModel(): CheckerType =
 | 
				
			||||||
 | 
					        CheckerType(
 | 
				
			||||||
 | 
					            id = null,
 | 
				
			||||||
 | 
					            name = this.name,
 | 
				
			||||||
 | 
					            params = this.params
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.checkerType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class CheckerTypeRequest (
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val params: List<Field>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.checkerType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class CheckerTypeResponse (
 | 
				
			||||||
 | 
					    val id: String? = null,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val params: List<Field>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.developer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Developer
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.DeveloperService
 | 
				
			||||||
 | 
					import org.springframework.http.HttpStatus
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.GetMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PathVariable
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestBody
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					import org.springframework.web.server.ResponseStatusException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("${Constants.API_BASE_PATH}/developer")
 | 
				
			||||||
 | 
					class DeveloperController(
 | 
				
			||||||
 | 
					    private val developerService: DeveloperService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @PostMapping
 | 
				
			||||||
 | 
					    fun create(@RequestBody developerRequest: DeveloperRequest): DeveloperResponse {
 | 
				
			||||||
 | 
					        return try {
 | 
				
			||||||
 | 
					            developerService.create(developerRequest.toModel())
 | 
				
			||||||
 | 
					                .toResponse()
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create developer: ${e.message}", e)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping
 | 
				
			||||||
 | 
					    fun getAll(): List<DeveloperResponse> {
 | 
				
			||||||
 | 
					        return try {
 | 
				
			||||||
 | 
					            developerService.getAll()
 | 
				
			||||||
 | 
					                .map { it.toResponse() }
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot get developers: ${e.message}", e)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/{id}")
 | 
				
			||||||
 | 
					    fun getById(@PathVariable id: String): DeveloperResponse {
 | 
				
			||||||
 | 
					        return developerService.getById(id)
 | 
				
			||||||
 | 
					            ?.toResponse()
 | 
				
			||||||
 | 
					            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "Developer not found")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun Developer.toResponse(): DeveloperResponse = DeveloperResponse(
 | 
				
			||||||
 | 
					        id = this.id ?: "",
 | 
				
			||||||
 | 
					        name = this.name,
 | 
				
			||||||
 | 
					        legalName = this.legalName,
 | 
				
			||||||
 | 
					        webUrl = this.webUrl,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun DeveloperRequest.toModel(): Developer = Developer(
 | 
				
			||||||
 | 
					        name = this.name,
 | 
				
			||||||
 | 
					        legalName = this.legalName,
 | 
				
			||||||
 | 
					        webUrl = this.webUrl,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.developer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class DeveloperRequest(
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val legalName: String,
 | 
				
			||||||
 | 
					    val webUrl: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.developer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class DeveloperResponse(
 | 
				
			||||||
 | 
					    val id: String,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val legalName: String,
 | 
				
			||||||
 | 
					    val webUrl: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -15,7 +15,7 @@ import org.springframework.web.server.ResponseStatusException
 | 
				
			|||||||
@RequestMapping("${Constants.API_BASE_PATH}/source")
 | 
					@RequestMapping("${Constants.API_BASE_PATH}/source")
 | 
				
			||||||
class SourceController(private val sourceService: SourceService) {
 | 
					class SourceController(private val sourceService: SourceService) {
 | 
				
			||||||
    @PostMapping
 | 
					    @PostMapping
 | 
				
			||||||
    suspend fun create(@RequestBody sourceRequest: SourceRequest): SourceResponse {
 | 
					    fun create(@RequestBody sourceRequest: SourceRequest): SourceResponse {
 | 
				
			||||||
        return try {
 | 
					        return try {
 | 
				
			||||||
            sourceService.create(sourceRequest.toModel())
 | 
					            sourceService.create(sourceRequest.toModel())
 | 
				
			||||||
                .toResponse()
 | 
					                .toResponse()
 | 
				
			||||||
@@ -25,9 +25,9 @@ class SourceController(private val sourceService: SourceService) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping
 | 
					    @GetMapping
 | 
				
			||||||
    fun getAll(): List<Source> {
 | 
					    fun getAll(): List<SourceResponse> {
 | 
				
			||||||
        return try {
 | 
					        return try {
 | 
				
			||||||
            sourceService.findALl()
 | 
					            sourceService.findALl().map { it.toResponse() }
 | 
				
			||||||
        } catch (e: Exception) {
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
            throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Cannot retrieve sources: ${e.message}", e)
 | 
					            throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Cannot retrieve sources: ${e.message}", e)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Type
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.TypeService
 | 
				
			||||||
 | 
					import org.springframework.http.HttpStatus
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.GetMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PathVariable
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestBody
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					import org.springframework.web.server.ResponseStatusException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("${Constants.API_BASE_PATH}/type")
 | 
				
			||||||
 | 
					class TypeController(
 | 
				
			||||||
 | 
					    private val typeService: TypeService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    @PostMapping
 | 
				
			||||||
 | 
					    fun createType(@RequestBody type: TypeRequest): TypeResponse? {
 | 
				
			||||||
 | 
					        return typeService.create(type.toModel())
 | 
				
			||||||
 | 
					            ?.toResponse()
 | 
				
			||||||
 | 
					            ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping
 | 
				
			||||||
 | 
					    fun getAllTypes(): List<TypeResponse> {
 | 
				
			||||||
 | 
					        return typeService.findAll().map{ it.toResponse() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @GetMapping("/{id}")
 | 
				
			||||||
 | 
					    fun getType(@PathVariable id: String): TypeResponse? {
 | 
				
			||||||
 | 
					        return typeService.findById(id)
 | 
				
			||||||
 | 
					            ?.toResponse()
 | 
				
			||||||
 | 
					            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun TypeRequest.toModel(): Type =
 | 
				
			||||||
 | 
					        Type(
 | 
				
			||||||
 | 
					            id = null,
 | 
				
			||||||
 | 
					            shortName = this.shortName,
 | 
				
			||||||
 | 
					            name = this.name,
 | 
				
			||||||
 | 
					            configFields = this.configFields,
 | 
				
			||||||
 | 
					            appFields = this.appFields
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun Type.toResponse(): TypeResponse =
 | 
				
			||||||
 | 
					        TypeResponse(
 | 
				
			||||||
 | 
					            id = this.id,
 | 
				
			||||||
 | 
					            shortName = this.shortName,
 | 
				
			||||||
 | 
					            name = this.name,
 | 
				
			||||||
 | 
					            configFields = this.configFields,
 | 
				
			||||||
 | 
					            appFields = this.appFields
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class TypeRequest(
 | 
				
			||||||
 | 
					    val shortName: String,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val configFields: List<Field>,
 | 
				
			||||||
 | 
					    val appFields: List<Field>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller.type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class TypeResponse(
 | 
				
			||||||
 | 
					    val id: String? = null,
 | 
				
			||||||
 | 
					    val shortName: String,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val configFields: List<Field>,
 | 
				
			||||||
 | 
					    val appFields: List<Field>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.controller.user
 | 
					package net.xintanalabs.rssotto.controller.user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import net.xintanalabs.rssotto.model.Role
 | 
					import net.xintanalabs.rssotto.enums.Role
 | 
				
			||||||
import net.xintanalabs.rssotto.model.User
 | 
					import net.xintanalabs.rssotto.model.User
 | 
				
			||||||
import net.xintanalabs.rssotto.service.UserService
 | 
					import net.xintanalabs.rssotto.service.UserService
 | 
				
			||||||
import org.springframework.http.HttpStatus
 | 
					import org.springframework.http.HttpStatus
 | 
				
			||||||
@@ -26,8 +26,8 @@ class UserController(private val userService: UserService) {
 | 
				
			|||||||
            ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user")
 | 
					            ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping
 | 
					    @GetMapping
 | 
				
			||||||
    fun listAll(): List<UserResponse> = listOf()
 | 
					    fun listAll(): List<UserResponse> =
 | 
				
			||||||
        //userService.findAll().map { it.toResponse() }
 | 
					        userService.findAll().map { it.toResponse() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping("/{uuid}")
 | 
					    @GetMapping("/{uuid}")
 | 
				
			||||||
    fun findById(@PathVariable uuid: UUID): UserResponse {
 | 
					    fun findById(@PathVariable uuid: UUID): UserResponse {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.db.mongodb
 | 
					package net.xintanalabs.rssotto.db.mongodb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired
 | 
					import org.springframework.data.mongodb.core.query.Criteria
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class MongoDBAbstract<T: Any>  (
 | 
					abstract class MongoDBAbstract<T: Any>  (
 | 
				
			||||||
    private val mongoDBClient: MongoDBClient
 | 
					    private val mongoDBClient: MongoDBClient
 | 
				
			||||||
@@ -9,23 +9,31 @@ abstract class MongoDBAbstract<T: Any>  (
 | 
				
			|||||||
    protected abstract val entityClass: Class<T>
 | 
					    protected abstract val entityClass: Class<T>
 | 
				
			||||||
    protected val idField: String = "id"
 | 
					    protected val idField: String = "id"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected fun create(entity: T): T {
 | 
					    protected fun createEntity(entity: T): T {
 | 
				
			||||||
        return mongoDBClient.insert(collection, entity, entityClass)
 | 
					        return mongoDBClient.insert(collection, entity, entityClass)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected fun getAll(): List<T> {
 | 
					    protected fun getAllEntities(): List<T> {
 | 
				
			||||||
        return mongoDBClient.findAll(collection, entityClass)
 | 
					        return mongoDBClient.findAll(collection, entityClass)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected fun getById(id: String): T? {
 | 
					    protected fun deleteEntitiesByCriteria(criteria: Criteria): Long {
 | 
				
			||||||
 | 
					        return mongoDBClient.deleteMany(collection, criteria, entityClass)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected fun getEntityById(id: String): T? {
 | 
				
			||||||
        return mongoDBClient.findOne(collection, idField, id, entityClass)
 | 
					        return mongoDBClient.findOne(collection, idField, id, entityClass)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected fun delete(id: String): Long {
 | 
					    protected fun deleteEntity(id: String): Long {
 | 
				
			||||||
        return mongoDBClient.deleteOne(collection, idField, id, entityClass)
 | 
					        return mongoDBClient.deleteOne(collection, idField, id, entityClass)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected fun update(id: String, updateFields: Map<String, Any>): Long {
 | 
					    protected fun updateEntity(id: String, updateFields: Map<String, Any>): Long {
 | 
				
			||||||
        return mongoDBClient.updateOne(collection, idField, id, updateFields, entityClass)
 | 
					        return mongoDBClient.updateOne(collection, idField, id, updateFields, entityClass)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected fun findEntitiesByCriteria(criteria: Criteria): List<T> {
 | 
				
			||||||
 | 
					        return mongoDBClient.findByFilter(collection, criteria, entityClass)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -54,6 +54,11 @@ class MongoDBClient(private val mongoTemplate: MongoTemplate) {
 | 
				
			|||||||
        return mongoTemplate.remove(query, clazz, collectionName).deletedCount
 | 
					        return mongoTemplate.remove(query, clazz, collectionName).deletedCount
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun <T: Any> deleteMany(collectionName: String, criteria: Criteria, clazz: Class<T>): Long {
 | 
				
			||||||
 | 
					        val query = Query(criteria)
 | 
				
			||||||
 | 
					        return mongoTemplate.remove(query, clazz, collectionName).deletedCount
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun <T: Any, R: Any> aggregate(
 | 
					    fun <T: Any, R: Any> aggregate(
 | 
				
			||||||
        collectionName: String,
 | 
					        collectionName: String,
 | 
				
			||||||
        aggregation: Aggregation,
 | 
					        aggregation: Aggregation,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								src/main/kotlin/net/xintanalabs/rssotto/enums/ModType.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/main/kotlin/net/xintanalabs/rssotto/enums/ModType.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.enums
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class ModType {
 | 
				
			||||||
 | 
					    AIRCRAFT, AIRPORT, SCENERY, DESKTOP, UTILITY, OTHER, NOT_SET
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.enums
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class NotificationType {
 | 
				
			||||||
 | 
					    VERSION_MISMATCH,
 | 
				
			||||||
 | 
					    TIMEOUT,
 | 
				
			||||||
 | 
					    UNREACHABLE,
 | 
				
			||||||
 | 
					    CHECK_FAILED,
 | 
				
			||||||
 | 
					    ONE_TIME_TASK_MISCONFIG
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								src/main/kotlin/net/xintanalabs/rssotto/enums/Role.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/main/kotlin/net/xintanalabs/rssotto/enums/Role.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.enums
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class Role {
 | 
				
			||||||
 | 
					    USER, ADMIN
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.enums
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class Severity {
 | 
				
			||||||
 | 
					    LOW, MEDIUM, HIGH, CRITICAL
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/App.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/App.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonInclude
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.ModType
 | 
				
			||||||
 | 
					import org.springframework.data.annotation.Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonInclude(JsonInclude.Include.NON_NULL)
 | 
				
			||||||
 | 
					data class App(
 | 
				
			||||||
 | 
					    @Id val id: String? = null,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val type: String = "",
 | 
				
			||||||
 | 
					    val source: String,
 | 
				
			||||||
 | 
					    val params: Map<String, String>,
 | 
				
			||||||
 | 
					    val fields: Map<String, String>,
 | 
				
			||||||
 | 
					    val downloadUrl: String,
 | 
				
			||||||
 | 
					    val latestVersion: String? = null,
 | 
				
			||||||
 | 
					    val createdAt: Long = 0,
 | 
				
			||||||
 | 
					    val updatedAt: Long = 0,
 | 
				
			||||||
 | 
					    val lastCheckedAt: Long = 0,
 | 
				
			||||||
 | 
					    val active: Boolean = true,
 | 
				
			||||||
 | 
					    val developer: String? = null,
 | 
				
			||||||
 | 
					    val modType: ModType? = null
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/CheckerType.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/CheckerType.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.fasterxml.jackson.annotation.JsonInclude
 | 
				
			||||||
 | 
					import org.springframework.data.annotation.Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsonInclude(JsonInclude.Include.NON_NULL)
 | 
				
			||||||
 | 
					data class CheckerType(
 | 
				
			||||||
 | 
					    @Id val id: String? = null,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val params: List<Field>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/Developer.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/Developer.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.data.annotation.Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class Developer(
 | 
				
			||||||
 | 
					    @Id val id: String? = null,
 | 
				
			||||||
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    val legalName: String,
 | 
				
			||||||
 | 
					    val webUrl: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.NotificationType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.Severity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class Notification(
 | 
				
			||||||
 | 
					    val id: String? = null,
 | 
				
			||||||
 | 
					    val appId: String? = null,
 | 
				
			||||||
 | 
					    val title: String,
 | 
				
			||||||
 | 
					    val type: NotificationType,
 | 
				
			||||||
 | 
					    val message: String,
 | 
				
			||||||
 | 
					    val description: String? = null,
 | 
				
			||||||
 | 
					    val stackTrace: String? = null,
 | 
				
			||||||
 | 
					    val severity: Severity,
 | 
				
			||||||
 | 
					    val createdAt: Long = 0,
 | 
				
			||||||
 | 
					    val read: Boolean = false
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/SiteCache.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/main/kotlin/net/xintanalabs/rssotto/model/SiteCache.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.data.annotation.Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class SiteCache(
 | 
				
			||||||
 | 
					    @Id val id: String? = null,
 | 
				
			||||||
 | 
					    val developerId: String,
 | 
				
			||||||
 | 
					    val html: String,
 | 
				
			||||||
 | 
					    val createdAt: Long = 0,
 | 
				
			||||||
 | 
					    val expiresAt: Long = 0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.model
 | 
					package net.xintanalabs.rssotto.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.Role
 | 
				
			||||||
import java.util.UUID
 | 
					import java.util.UUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class User(
 | 
					data class User(
 | 
				
			||||||
@@ -7,9 +8,4 @@ data class User(
 | 
				
			|||||||
    val username: String,
 | 
					    val username: String,
 | 
				
			||||||
    val password: String,
 | 
					    val password: String,
 | 
				
			||||||
    val role: Role
 | 
					    val role: Role
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum class Role {
 | 
					 | 
				
			||||||
    USER, ADMIN
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.App
 | 
				
			||||||
 | 
					import org.springframework.data.mongodb.core.query.Criteria
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					class AppRepository(
 | 
				
			||||||
 | 
					    mongoDBClient: MongoDBClient
 | 
				
			||||||
 | 
					): MongoDBAbstract<App>(mongoDBClient) {
 | 
				
			||||||
 | 
					    override val collection: String = Constants.COLLECTION_APPS
 | 
				
			||||||
 | 
					    override val entityClass: Class<App> = App::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createApp(app: App): App {
 | 
				
			||||||
 | 
					        return createEntity(app)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAllApps(): List<App> {
 | 
				
			||||||
 | 
					        return getAllEntities()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAppById(id: String): App? {
 | 
				
			||||||
 | 
					        return getEntityById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun deleteApp(id: String): Long {
 | 
				
			||||||
 | 
					        return deleteEntity(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateApp(id: String, updateFields: Map<String, Any>): Long {
 | 
				
			||||||
 | 
					        return updateEntity(id, updateFields)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun findAppsByCriteria(criteria: Criteria): List<App> {
 | 
				
			||||||
 | 
					        return findEntitiesByCriteria(criteria)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.CheckerType
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					class CheckerTypeRepository(
 | 
				
			||||||
 | 
					    mongoDBClient: MongoDBClient
 | 
				
			||||||
 | 
					): MongoDBAbstract<CheckerType>(mongoDBClient) {
 | 
				
			||||||
 | 
					    override val collection: String = Constants.COLLECTION_CHECKER_TYPES
 | 
				
			||||||
 | 
					    override val entityClass: Class<CheckerType> = CheckerType::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createCheckerType(checkerType: CheckerType): CheckerType {
 | 
				
			||||||
 | 
					        return createEntity(checkerType)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAllCheckerTypes(): List<CheckerType> {
 | 
				
			||||||
 | 
					        return try {
 | 
				
			||||||
 | 
					            getAllEntities()
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            println("Error fetching all checker types: ${e.message}")
 | 
				
			||||||
 | 
					            throw e
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getCheckerTypeById(id: String): CheckerType? {
 | 
				
			||||||
 | 
					        return getEntityById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Developer
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					class DeveloperRepository(
 | 
				
			||||||
 | 
					    mongoDBClient: MongoDBClient
 | 
				
			||||||
 | 
					): MongoDBAbstract<Developer>(mongoDBClient) {
 | 
				
			||||||
 | 
					    override val collection: String = Constants.COLLECTION_DEVELOPERS
 | 
				
			||||||
 | 
					    override val entityClass: Class<Developer> = Developer::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun create(developer: Developer): Developer {
 | 
				
			||||||
 | 
					        return createEntity(developer)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAll(): List<Developer> {
 | 
				
			||||||
 | 
					        return getAllEntities()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getById(id: String): Developer? {
 | 
				
			||||||
 | 
					        return getEntityById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Notification
 | 
				
			||||||
 | 
					import org.springframework.data.mongodb.core.query.Criteria
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					class NotificationRepository(
 | 
				
			||||||
 | 
					    mongoDBClient: MongoDBClient
 | 
				
			||||||
 | 
					): MongoDBAbstract<Notification>(mongoDBClient) {
 | 
				
			||||||
 | 
					    override val collection: String = Constants.COLLECTION_NOTIFICATIONS
 | 
				
			||||||
 | 
					    override val entityClass: Class<Notification> = Notification::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createNotification(notification: Notification): Notification {
 | 
				
			||||||
 | 
					        return createEntity(notification)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAllNotifications(): List<Notification> {
 | 
				
			||||||
 | 
					        return getAllEntities()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fun getNotificationById(id: String): Notification? {
 | 
				
			||||||
 | 
					        return getEntityById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fun getNotificationByFilter(criteria: Criteria): List<Notification> {
 | 
				
			||||||
 | 
					        return findEntitiesByCriteria(criteria)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.SiteCache
 | 
				
			||||||
 | 
					import org.springframework.data.mongodb.core.query.Criteria
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					class SiteCacheRepository(
 | 
				
			||||||
 | 
					    mongoDBClient: MongoDBClient
 | 
				
			||||||
 | 
					): MongoDBAbstract<SiteCache>(mongoDBClient) {
 | 
				
			||||||
 | 
					    override val collection = Constants.COLLECTION_SITE_CACHE
 | 
				
			||||||
 | 
					    override val entityClass = SiteCache::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun create(siteCache: SiteCache): SiteCache {
 | 
				
			||||||
 | 
					        return createEntity(siteCache)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getByDeveloperId(developerId: String): SiteCache? {
 | 
				
			||||||
 | 
					        val criteria = Criteria.where("developerId").`is`(developerId)
 | 
				
			||||||
 | 
					        return findEntitiesByCriteria(criteria).firstOrNull()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun deleteAll(): Long {
 | 
				
			||||||
 | 
					        return deleteEntitiesByCriteria(Criteria())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun deleteById(id: String): Long {
 | 
				
			||||||
 | 
					        return deleteEntity(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,7 +4,6 @@ import net.xintanalabs.rssotto.constants.Constants
 | 
				
			|||||||
import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBAbstract
 | 
				
			||||||
import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
					import net.xintanalabs.rssotto.db.mongodb.MongoDBClient
 | 
				
			||||||
import net.xintanalabs.rssotto.model.Source
 | 
					import net.xintanalabs.rssotto.model.Source
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Repository
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Repository
 | 
					@Repository
 | 
				
			||||||
@@ -15,14 +14,15 @@ class SourceRepository (
 | 
				
			|||||||
    override val entityClass: Class<Source> = Source::class.java
 | 
					    override val entityClass: Class<Source> = Source::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun createSource(source: Source): Source {
 | 
					    fun createSource(source: Source): Source {
 | 
				
			||||||
        return create(source)
 | 
					        return createEntity(source)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getAllSources(): List<Source> {
 | 
					    fun getAllSources(): List<Source> {
 | 
				
			||||||
        return getAll()
 | 
					        return getAllEntities()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getSourceById(id: String): Source? {
 | 
					    fun getSourceById(id: String): Source? {
 | 
				
			||||||
        return getById(id)
 | 
					        return getEntityById(id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,19 +7,21 @@ import net.xintanalabs.rssotto.model.Type
 | 
				
			|||||||
import org.springframework.stereotype.Repository
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Repository
 | 
					@Repository
 | 
				
			||||||
class TypeRepository(mongoDBClient: MongoDBClient): MongoDBAbstract<Type>(mongoDBClient) {
 | 
					class TypeRepository(
 | 
				
			||||||
 | 
					    mongoDBClient: MongoDBClient
 | 
				
			||||||
 | 
					): MongoDBAbstract<Type>(mongoDBClient) {
 | 
				
			||||||
    override val collection: String = Constants.COLLECTION_TYPES
 | 
					    override val collection: String = Constants.COLLECTION_TYPES
 | 
				
			||||||
    override val entityClass: Class<Type> = Type::class.java
 | 
					    override val entityClass: Class<Type> = Type::class.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun createType(type: Type): Type {
 | 
					    fun create(type: Type): Type {
 | 
				
			||||||
        return create(type)
 | 
					        return createEntity(type)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun getAllTypes(): List<Type> {
 | 
					    fun getAll(): List<Type> {
 | 
				
			||||||
        return getAll()
 | 
					        return getAllEntities()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun getTypeById(id: String): Type? {
 | 
					    fun getById(id: String): Type? {
 | 
				
			||||||
        return getById(id)
 | 
					        return getEntityById(id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.repository
 | 
					package net.xintanalabs.rssotto.repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import net.xintanalabs.rssotto.model.Role
 | 
					import net.xintanalabs.rssotto.enums.Role
 | 
				
			||||||
import net.xintanalabs.rssotto.model.User
 | 
					import net.xintanalabs.rssotto.model.User
 | 
				
			||||||
import org.springframework.security.crypto.password.PasswordEncoder
 | 
					import org.springframework.security.crypto.password.PasswordEncoder
 | 
				
			||||||
import org.springframework.stereotype.Repository
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.runBlocking
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checker.exceptions.CheckerException
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checkers.CheckerFactory
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.controller.app.AppVersionResponse
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.NotificationType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.enums.Severity
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.App
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.CheckerType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Notification
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Source
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class ActionsService(
 | 
				
			||||||
 | 
					    private val checkerFactory: CheckerFactory,
 | 
				
			||||||
 | 
					    private val appService: AppService,
 | 
				
			||||||
 | 
					    private val sourceService: SourceService,
 | 
				
			||||||
 | 
					    private val checkerTypeService: CheckerTypeService,
 | 
				
			||||||
 | 
					    private val notificationService: NotificationService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val log = LoggerFactory.getLogger(ActionsService::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun checkVersions(idList: List<String>): List<AppVersionResponse> {
 | 
				
			||||||
 | 
					        val apps = appService.getAppsByIds(idList)
 | 
				
			||||||
 | 
					        return checkApps(apps)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun checkAllVersions(): List<AppVersionResponse> {
 | 
				
			||||||
 | 
					        val apps = appService.getAllApps()
 | 
				
			||||||
 | 
					        return checkApps(apps)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun checkApps(apps: List<App>): List<AppVersionResponse> {
 | 
				
			||||||
 | 
					        val sources = sourceService.findALl()
 | 
				
			||||||
 | 
					        val checkerTypes = checkerTypeService.findAll()
 | 
				
			||||||
 | 
					        val versionList = mutableListOf<AppVersionResponse>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        apps.forEach {
 | 
				
			||||||
 | 
					                app ->
 | 
				
			||||||
 | 
					            if (app.id != null && app.active) {
 | 
				
			||||||
 | 
					                val appLatestVersion: String? = getLatestAppVersion(app, sources, checkerTypes)
 | 
				
			||||||
 | 
					                versionList.add(
 | 
				
			||||||
 | 
					                    AppVersionResponse(
 | 
				
			||||||
 | 
					                        id = app.id,
 | 
				
			||||||
 | 
					                        latestVersion = appLatestVersion
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return versionList
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getLatestAppVersion(
 | 
				
			||||||
 | 
					        app: App,
 | 
				
			||||||
 | 
					        sources: List<Source>,
 | 
				
			||||||
 | 
					        checkerTypes: List<CheckerType>
 | 
				
			||||||
 | 
					    ): String? {
 | 
				
			||||||
 | 
					        val source: Source? = sources.find { s -> s.id == app.source }
 | 
				
			||||||
 | 
					        var appVersion: String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (source != null) {
 | 
				
			||||||
 | 
					            val checkerType = checkerTypes.find { ct -> ct.id == source.checkerType }
 | 
				
			||||||
 | 
					            if (checkerType != null) {
 | 
				
			||||||
 | 
					                val checker = checkerFactory.createChecker(checkerType.name)
 | 
				
			||||||
 | 
					                val paramsMap = getParamsMap(app, checkerType, source);
 | 
				
			||||||
 | 
					                appVersion = runBlocking {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        checker.getLatestVersion(paramsMap)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    catch (e: CheckerException) {
 | 
				
			||||||
 | 
					                        val notification = Notification(
 | 
				
			||||||
 | 
					                            appId = app.id,
 | 
				
			||||||
 | 
					                            title = "Error checking version for app ${app.name}",
 | 
				
			||||||
 | 
					                            type = NotificationType.CHECK_FAILED,
 | 
				
			||||||
 | 
					                            message = e.message?:"Unknown error",
 | 
				
			||||||
 | 
					                            description = e.info?.entries?.joinToString("\n") { "${it.key}: ${it.value}" },
 | 
				
			||||||
 | 
					                            stackTrace = e.stackTrace.joinToString("\n"),
 | 
				
			||||||
 | 
					                            severity = Severity.HIGH,
 | 
				
			||||||
 | 
					                            createdAt = System.currentTimeMillis()
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        notificationService.create(notification)
 | 
				
			||||||
 | 
					                        log.error("Error checking version for app ${app.name}: ${e.message}")
 | 
				
			||||||
 | 
					                        null
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (appVersion != null) {
 | 
				
			||||||
 | 
					                log.info("App (${app.name}, latest versión ${appVersion}")
 | 
				
			||||||
 | 
					                appService.updateLatestVersion(app.id!!, appVersion)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return appVersion
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getParamsMap(app: App, checkerType: CheckerType, source: Source): Map<String, String> {
 | 
				
			||||||
 | 
					        val paramsMap = mutableMapOf<String, String>()
 | 
				
			||||||
 | 
					        checkerType.params.forEach { field ->
 | 
				
			||||||
 | 
					            val value = getValue(field.name, app, source)
 | 
				
			||||||
 | 
					            paramsMap[field.name] = value
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return additionalParams(paramsMap, app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun additionalParams(map: MutableMap<String, String>, app: App): Map<String, String> {
 | 
				
			||||||
 | 
					        if (map.keys.contains("cached")) {
 | 
				
			||||||
 | 
					            map["developerId"] = app.developer ?: ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return map
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getValue(field: String, app: App, source: Source): String {
 | 
				
			||||||
 | 
					        if (field == "id") {
 | 
				
			||||||
 | 
					            return app.id ?: ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var value: String? = app.params[field]
 | 
				
			||||||
 | 
					        if (value == null) {
 | 
				
			||||||
 | 
					            value = source.defaults[field]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return value ?: ""
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.App
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.repository.AppRepository
 | 
				
			||||||
 | 
					import org.springframework.data.mongodb.core.query.Criteria
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class AppService(
 | 
				
			||||||
 | 
					    private val appRepository: AppRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createApp(app: App): App? {
 | 
				
			||||||
 | 
					        return appRepository.createApp(app)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAllApps(): List<App> {
 | 
				
			||||||
 | 
					        return appRepository.getAllApps()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAppById(id: String): App? {
 | 
				
			||||||
 | 
					        return appRepository.getAppById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAppsByIds(ids: List<String>): List<App> {
 | 
				
			||||||
 | 
					        val criteria = Criteria.where("_id").`in`(ids)
 | 
				
			||||||
 | 
					        return appRepository.findAppsByCriteria(criteria)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAppsByVersions(versions: List<String>): List<App> {
 | 
				
			||||||
 | 
					        val criteria = Criteria.where("latestVersion").`in`(versions)
 | 
				
			||||||
 | 
					        return appRepository.findAppsByCriteria(criteria)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun deleteApp(id: String): Long {
 | 
				
			||||||
 | 
					        return appRepository.deleteApp(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateApp(id: String, updateFields: Map<String, Any>): Long {
 | 
				
			||||||
 | 
					        return appRepository.updateApp(id, updateFields)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateAppDetails(id: String, newName: String?): Long {
 | 
				
			||||||
 | 
					        val updateFields = mutableMapOf<String, Any>()
 | 
				
			||||||
 | 
					        newName?.let { updateFields["name"] = it }
 | 
				
			||||||
 | 
					        updateFields["updatedAt"] = System.currentTimeMillis()
 | 
				
			||||||
 | 
					        return appRepository.updateApp(id, updateFields)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateLatestVersion(appId: String, latestVersion: String): Long {
 | 
				
			||||||
 | 
					        val updateFields = mapOf(
 | 
				
			||||||
 | 
					            "latestVersion" to latestVersion,
 | 
				
			||||||
 | 
					            "lastCheckedAt" to System.currentTimeMillis()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return appRepository.updateApp(appId, updateFields)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun searchApps(query: String): List<App> {
 | 
				
			||||||
 | 
					        val regex = ".*${Regex.escape(query)}.*"
 | 
				
			||||||
 | 
					        val criteria = Criteria.where("name").regex(regex, "i")
 | 
				
			||||||
 | 
					        return appRepository.findAppsByCriteria(criteria)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.CheckerType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.repository.CheckerTypeRepository
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class CheckerTypeService(
 | 
				
			||||||
 | 
					    private val checkerTypeRepository: CheckerTypeRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun findAll(): List<CheckerType> {
 | 
				
			||||||
 | 
					        return checkerTypeRepository.getAllCheckerTypes()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun findById(id: String): CheckerType? {
 | 
				
			||||||
 | 
					        return checkerTypeRepository.getCheckerTypeById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun create(checkerType: CheckerType): CheckerType? {
 | 
				
			||||||
 | 
					        return checkerTypeRepository.createCheckerType(checkerType)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Developer
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.repository.DeveloperRepository
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class DeveloperService(
 | 
				
			||||||
 | 
					    private val developerRepository: DeveloperRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun getAll(): List<Developer> {
 | 
				
			||||||
 | 
					        return developerRepository.getAll()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getById(id: String): Developer? {
 | 
				
			||||||
 | 
					        return developerRepository.getById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun create(developer: Developer): Developer {
 | 
				
			||||||
 | 
					        return developerRepository.create(developer)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Notification
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.repository.NotificationRepository
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class NotificationService(
 | 
				
			||||||
 | 
					    private val notificationRepository: NotificationRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    private val log = LoggerFactory.getLogger(NotificationService::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun create(notification: Notification): Notification {
 | 
				
			||||||
 | 
					        return notificationRepository.createNotification(notification)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.SiteCache
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.repository.SiteCacheRepository
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class SiteCacheService(
 | 
				
			||||||
 | 
					    private val siteCacheRepository: SiteCacheRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun clearCache() {
 | 
				
			||||||
 | 
					        siteCacheRepository.deleteAll()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getCacheByDeveloperId(developerId: String): SiteCache? =
 | 
				
			||||||
 | 
					        siteCacheRepository.getByDeveloperId(developerId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun create(siteCache: SiteCache): SiteCache =
 | 
				
			||||||
 | 
					        siteCacheRepository.create(siteCache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun isCacheValid(siteCache: SiteCache): Boolean {
 | 
				
			||||||
 | 
					        val currentTime = System.currentTimeMillis()
 | 
				
			||||||
 | 
					        return currentTime < siteCache.expiresAt
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun deleteById(id: String) {
 | 
				
			||||||
 | 
					        siteCacheRepository.deleteById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Type
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.repository.TypeRepository
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class TypeService(
 | 
				
			||||||
 | 
					    private val typeRepository: TypeRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    fun create(type: Type): Type? {
 | 
				
			||||||
 | 
					        return typeRepository.create(type)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun findAll(): List<Type> {
 | 
				
			||||||
 | 
					        return typeRepository.getAll()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun findById(id: String): Type? {
 | 
				
			||||||
 | 
					        return typeRepository.getById(id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.tasks
 | 
					package net.xintanalabs.rssotto.tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.config.RssottoProperties
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.service.ActionsService
 | 
				
			||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import org.springframework.scheduling.annotation.Scheduled
 | 
					import org.springframework.scheduling.annotation.Scheduled
 | 
				
			||||||
import org.springframework.stereotype.Component
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
@@ -7,12 +9,18 @@ import java.text.SimpleDateFormat
 | 
				
			|||||||
import java.util.Date
 | 
					import java.util.Date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
class ScheduledTasks {
 | 
					class ScheduledTasks(
 | 
				
			||||||
 | 
					    private val actionsService: ActionsService
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    private val log = LoggerFactory.getLogger(ScheduledTasks::class.java)
 | 
					    private val log = LoggerFactory.getLogger(ScheduledTasks::class.java)
 | 
				
			||||||
    private val dateFormat = SimpleDateFormat("HH:mm:ss")
 | 
					    private val dateFormat = SimpleDateFormat("HH:mm:ss")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Scheduled(fixedRate = 500000)
 | 
					    //@Scheduled(cron = "\${rssotto.check-task-cron}")
 | 
				
			||||||
    fun reportCurrentTime() {
 | 
					    @Scheduled(fixedDelayString = "\${rssotto.check-task-fixed-delay}", initialDelayString = "\${rssotto.check-task-initial-delay}")
 | 
				
			||||||
        log.info("The time is now {}", dateFormat.format(Date()))
 | 
					
 | 
				
			||||||
 | 
					    fun checkApps() {
 | 
				
			||||||
 | 
					        log.info("Check task started at {}", dateFormat.format(Date()))
 | 
				
			||||||
 | 
					        actionsService.checkAllVersions()
 | 
				
			||||||
 | 
					        log.info("Check task completed at {}", dateFormat.format(Date()))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -25,7 +25,9 @@ logging:
 | 
				
			|||||||
          mongodb: DEBUG
 | 
					          mongodb: DEBUG
 | 
				
			||||||
        security: DEBUG
 | 
					        security: DEBUG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
version-checker:
 | 
					rssotto:
 | 
				
			||||||
  interval-minutes: 5
 | 
					  check-task-cron: "0 */30 * * * *"
 | 
				
			||||||
 | 
					  check-task-initial-delay: 10000
 | 
				
			||||||
 | 
					  check-task-fixed-delay: 21600000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,12 @@ package net.xintanalabs.rssotto
 | 
				
			|||||||
import org.junit.jupiter.api.Test
 | 
					import org.junit.jupiter.api.Test
 | 
				
			||||||
import org.springframework.boot.test.context.SpringBootTest
 | 
					import org.springframework.boot.test.context.SpringBootTest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootTest
 | 
					//SpringBootTest
 | 
				
			||||||
class RssottoApplicationTests {
 | 
					class RssottoApplicationTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						//Test
 | 
				
			||||||
	fun contextLoads() {
 | 
						fun contextLoads() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user