Compare commits

1 Commits
main ... dev

Author SHA1 Message Date
Jose Conde
36d8a1dd32 Adding Type and App endpoints 2025-09-24 17:43:45 +02:00
16 changed files with 370 additions and 8 deletions

View File

@@ -0,0 +1,90 @@
package net.xintanalabs.rssotto.controller.app
import net.xintanalabs.rssotto.constants.Constants
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("/versions")
fun getAppsVersions(@RequestParam version: List<String>): List<AppVersionResponse> {
return appService.getAppsByVersions(version).map { it.toVersionResponse() }
}
@GetMapping("/search")
fun searchApps(@RequestParam q: String): List<AppResponse> {
return appService.searchApps(q).map { it.toResponse() }
}
private fun App.toVersionResponse(): AppVersionResponse {
return AppVersionResponse(
id = this.id,
latestVersion = this.latestVersion,
)
}
private fun App.toResponse(): AppResponse {
return AppResponse(
id = this.id,
uid = this.uid,
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
)
}
private fun AppRequest.toModel(): App {
return App(
uid = this.uid,
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
)
}
}

View File

@@ -0,0 +1,16 @@
package net.xintanalabs.rssotto.controller.app
data class AppRequest(
val uid: String,
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
)

View File

@@ -0,0 +1,17 @@
package net.xintanalabs.rssotto.controller.app
data class AppResponse(
val id: String? = null,
val uid: String,
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
)

View File

@@ -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
)

View File

@@ -25,9 +25,9 @@ class SourceController(private val sourceService: SourceService) {
}
@GetMapping
fun getAll(): List<Source> {
fun getAll(): List<SourceResponse> {
return try {
sourceService.findALl()
sourceService.findALl().map { it.toResponse() }
} catch (e: Exception) {
throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Cannot retrieve sources: ${e.message}", e)
}

View File

@@ -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
)
}

View File

@@ -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>
)

View File

@@ -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>
)

View File

@@ -26,8 +26,8 @@ class UserController(private val userService: UserService) {
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user")
@GetMapping
fun listAll(): List<UserResponse> = listOf()
//userService.findAll().map { it.toResponse() }
fun listAll(): List<UserResponse> =
userService.findAll().map { it.toResponse() }
@GetMapping("/{uuid}")
fun findById(@PathVariable uuid: UUID): UserResponse {

View File

@@ -1,6 +1,7 @@
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> (
private val mongoDBClient: MongoDBClient
@@ -28,4 +29,8 @@ abstract class MongoDBAbstract<T: Any> (
protected fun update(id: String, updateFields: Map<String, Any>): Long {
return mongoDBClient.updateOne(collection, idField, id, updateFields, entityClass)
}
protected fun findByCriteria(criteria: Criteria): List<T> {
return mongoDBClient.findByFilter(collection, criteria, entityClass)
}
}

View File

@@ -0,0 +1,22 @@
package net.xintanalabs.rssotto.model
import com.fasterxml.jackson.annotation.JsonInclude
import org.springframework.data.annotation.Id
@JsonInclude(JsonInclude.Include.NON_NULL)
data class App(
@Id val id: String? = null,
val uid: String,
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
)

View File

@@ -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 create(app)
}
fun getAllApps(): List<App> {
return getAll()
}
fun getAppById(id: String): App? {
return getById(id)
}
fun deleteApp(id: String): Long {
return delete(id)
}
fun updateApp(id: String, updateFields: Map<String, Any>): Long {
return update(id, updateFields)
}
fun findAppsByCriteria(criteria: Criteria): List<App> {
return findByCriteria(criteria)
}
}

View File

@@ -25,4 +25,5 @@ class SourceRepository (
fun getSourceById(id: String): Source? {
return getById(id)
}
}

View File

@@ -7,19 +7,21 @@ import net.xintanalabs.rssotto.model.Type
import org.springframework.stereotype.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 entityClass: Class<Type> = Type::class.java
suspend fun createType(type: Type): Type {
fun createType(type: Type): Type {
return create(type)
}
suspend fun getAllTypes(): List<Type> {
fun getAllTypes(): List<Type> {
return getAll()
}
suspend fun getTypeById(id: String): Type? {
fun getTypeById(id: String): Type? {
return getById(id)
}
}

View File

@@ -0,0 +1,58 @@
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 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<String, Any>(
"latestVersion" to latestVersion
)
return appRepository.updateApp(appId, updateFields)
}
fun getAppsByVersions(versions: List<String>): List<App> {
val criteria = Criteria.where("latestVersion").`in`(versions)
return appRepository.findAppsByCriteria(criteria)
}
fun searchApps(query: String): List<App> {
val regex = ".*${Regex.escape(query)}.*"
val criteria = Criteria.where("name").regex(regex, "i")
return appRepository.findAppsByCriteria(criteria)
}
}

View File

@@ -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.createType(type)
}
fun findAll(): List<Type> {
return typeRepository.getAllTypes()
}
fun findById(id: String): Type? {
return typeRepository.getTypeById(id)
}
}