From 36d8a1dd32f8d897b1d5f919161abc4b3fa6d93c Mon Sep 17 00:00:00 2001 From: Jose Conde Date: Wed, 24 Sep 2025 17:43:45 +0200 Subject: [PATCH] Adding Type and App endpoints --- .../rssotto/controller/app/AppController.kt | 90 +++++++++++++++++++ .../rssotto/controller/app/AppRequest.kt | 16 ++++ .../rssotto/controller/app/AppResponse.kt | 17 ++++ .../controller/app/AppVersionResponse.kt | 8 ++ .../controller/source/SourceController.kt | 4 +- .../rssotto/controller/type/TypeController.kt | 60 +++++++++++++ .../rssotto/controller/type/TypeRequest.kt | 10 +++ .../rssotto/controller/type/TypeResponse.kt | 11 +++ .../rssotto/controller/user/UserController.kt | 4 +- .../rssotto/db/mongodb/MongoDBAbstract.kt | 5 ++ .../net/xintanalabs/rssotto/model/App.kt | 22 +++++ .../rssotto/repository/AppRepository.kt | 40 +++++++++ .../rssotto/repository/SourceRepository.kt | 1 + .../rssotto/repository/TypeRepository.kt | 10 ++- .../xintanalabs/rssotto/service/AppService.kt | 58 ++++++++++++ .../rssotto/service/TypeService.kt | 22 +++++ 16 files changed, 370 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppController.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppRequest.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppResponse.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppVersionResponse.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeController.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeRequest.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeResponse.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/model/App.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/repository/AppRepository.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/service/AppService.kt create mode 100644 src/main/kotlin/net/xintanalabs/rssotto/service/TypeService.kt diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppController.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppController.kt new file mode 100644 index 0000000..a8efeef --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppController.kt @@ -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 = + appService.getAllApps() + .map { it.toResponse() } + + @GetMapping("/versions") + fun getAppsVersions(@RequestParam version: List): List { + return appService.getAppsByVersions(version).map { it.toVersionResponse() } + } + + @GetMapping("/search") + fun searchApps(@RequestParam q: String): List { + 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 + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppRequest.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppRequest.kt new file mode 100644 index 0000000..e46c7c4 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppRequest.kt @@ -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, + val fields: Map, + val downloadUrl: String, + val latestVersion: String? = null, + val createdAt: Long = 0, + val updatedAt: Long = 0, + val lastCheckedAt: Long = 0, + val active: Boolean = true +) diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppResponse.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppResponse.kt new file mode 100644 index 0000000..0392fda --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppResponse.kt @@ -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, + val fields: Map, + val downloadUrl: String, + val latestVersion: String? = null, + val createdAt: Long = 0, + val updatedAt: Long = 0, + val lastCheckedAt: Long = 0, + val active: Boolean = true +) diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppVersionResponse.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppVersionResponse.kt new file mode 100644 index 0000000..3002bb0 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/app/AppVersionResponse.kt @@ -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 +) diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/source/SourceController.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/source/SourceController.kt index 3c4cc90..8629d17 100644 --- a/src/main/kotlin/net/xintanalabs/rssotto/controller/source/SourceController.kt +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/source/SourceController.kt @@ -25,9 +25,9 @@ class SourceController(private val sourceService: SourceService) { } @GetMapping - fun getAll(): List { + fun getAll(): List { return try { - sourceService.findALl() + sourceService.findALl().map { it.toResponse() } } catch (e: Exception) { throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Cannot retrieve sources: ${e.message}", e) } diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeController.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeController.kt new file mode 100644 index 0000000..5a82de6 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeController.kt @@ -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 { + 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 + ) +} + + + + diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeRequest.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeRequest.kt new file mode 100644 index 0000000..3536874 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeRequest.kt @@ -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, + val appFields: List +) diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeResponse.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeResponse.kt new file mode 100644 index 0000000..3b865f1 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/type/TypeResponse.kt @@ -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, + val appFields: List +) diff --git a/src/main/kotlin/net/xintanalabs/rssotto/controller/user/UserController.kt b/src/main/kotlin/net/xintanalabs/rssotto/controller/user/UserController.kt index 33747ef..c8ace96 100644 --- a/src/main/kotlin/net/xintanalabs/rssotto/controller/user/UserController.kt +++ b/src/main/kotlin/net/xintanalabs/rssotto/controller/user/UserController.kt @@ -26,8 +26,8 @@ class UserController(private val userService: UserService) { ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user") @GetMapping - fun listAll(): List = listOf() - //userService.findAll().map { it.toResponse() } + fun listAll(): List = + userService.findAll().map { it.toResponse() } @GetMapping("/{uuid}") fun findById(@PathVariable uuid: UUID): UserResponse { diff --git a/src/main/kotlin/net/xintanalabs/rssotto/db/mongodb/MongoDBAbstract.kt b/src/main/kotlin/net/xintanalabs/rssotto/db/mongodb/MongoDBAbstract.kt index 1cdcdb7..6b6310c 100644 --- a/src/main/kotlin/net/xintanalabs/rssotto/db/mongodb/MongoDBAbstract.kt +++ b/src/main/kotlin/net/xintanalabs/rssotto/db/mongodb/MongoDBAbstract.kt @@ -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 ( private val mongoDBClient: MongoDBClient @@ -28,4 +29,8 @@ abstract class MongoDBAbstract ( protected fun update(id: String, updateFields: Map): Long { return mongoDBClient.updateOne(collection, idField, id, updateFields, entityClass) } + + protected fun findByCriteria(criteria: Criteria): List { + return mongoDBClient.findByFilter(collection, criteria, entityClass) + } } \ No newline at end of file diff --git a/src/main/kotlin/net/xintanalabs/rssotto/model/App.kt b/src/main/kotlin/net/xintanalabs/rssotto/model/App.kt new file mode 100644 index 0000000..4d11a81 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/model/App.kt @@ -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, + val fields: Map, + val downloadUrl: String, + val latestVersion: String? = null, + val createdAt: Long = 0, + val updatedAt: Long = 0, + val lastCheckedAt: Long = 0, + val active: Boolean = true +) diff --git a/src/main/kotlin/net/xintanalabs/rssotto/repository/AppRepository.kt b/src/main/kotlin/net/xintanalabs/rssotto/repository/AppRepository.kt new file mode 100644 index 0000000..3d48903 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/repository/AppRepository.kt @@ -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(mongoDBClient) { + override val collection: String = Constants.COLLECTION_APPS + override val entityClass: Class = App::class.java + + fun createApp(app: App): App { + return create(app) + } + + fun getAllApps(): List { + return getAll() + } + + fun getAppById(id: String): App? { + return getById(id) + } + + fun deleteApp(id: String): Long { + return delete(id) + } + + fun updateApp(id: String, updateFields: Map): Long { + return update(id, updateFields) + } + + fun findAppsByCriteria(criteria: Criteria): List { + return findByCriteria(criteria) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/xintanalabs/rssotto/repository/SourceRepository.kt b/src/main/kotlin/net/xintanalabs/rssotto/repository/SourceRepository.kt index 6265ac4..11a0ff9 100644 --- a/src/main/kotlin/net/xintanalabs/rssotto/repository/SourceRepository.kt +++ b/src/main/kotlin/net/xintanalabs/rssotto/repository/SourceRepository.kt @@ -25,4 +25,5 @@ class SourceRepository ( fun getSourceById(id: String): Source? { return getById(id) } + } diff --git a/src/main/kotlin/net/xintanalabs/rssotto/repository/TypeRepository.kt b/src/main/kotlin/net/xintanalabs/rssotto/repository/TypeRepository.kt index ef8e43d..9411aeb 100644 --- a/src/main/kotlin/net/xintanalabs/rssotto/repository/TypeRepository.kt +++ b/src/main/kotlin/net/xintanalabs/rssotto/repository/TypeRepository.kt @@ -7,19 +7,21 @@ import net.xintanalabs.rssotto.model.Type import org.springframework.stereotype.Repository @Repository -class TypeRepository(mongoDBClient: MongoDBClient): MongoDBAbstract(mongoDBClient) { +class TypeRepository( + mongoDBClient: MongoDBClient +): MongoDBAbstract(mongoDBClient) { override val collection: String = Constants.COLLECTION_TYPES override val entityClass: Class = Type::class.java - suspend fun createType(type: Type): Type { + fun createType(type: Type): Type { return create(type) } - suspend fun getAllTypes(): List { + fun getAllTypes(): List { return getAll() } - suspend fun getTypeById(id: String): Type? { + fun getTypeById(id: String): Type? { return getById(id) } } \ No newline at end of file diff --git a/src/main/kotlin/net/xintanalabs/rssotto/service/AppService.kt b/src/main/kotlin/net/xintanalabs/rssotto/service/AppService.kt new file mode 100644 index 0000000..37e0a90 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/service/AppService.kt @@ -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 { + 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): Long { + return appRepository.updateApp(id, updateFields) + } + + fun updateAppDetails(id: String, newName: String?): Long { + val updateFields = mutableMapOf() + 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 + ) + return appRepository.updateApp(appId, updateFields) + } + + fun getAppsByVersions(versions: List): List { + val criteria = Criteria.where("latestVersion").`in`(versions) + return appRepository.findAppsByCriteria(criteria) + } + + fun searchApps(query: String): List { + val regex = ".*${Regex.escape(query)}.*" + val criteria = Criteria.where("name").regex(regex, "i") + return appRepository.findAppsByCriteria(criteria) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/xintanalabs/rssotto/service/TypeService.kt b/src/main/kotlin/net/xintanalabs/rssotto/service/TypeService.kt new file mode 100644 index 0000000..b1562e9 --- /dev/null +++ b/src/main/kotlin/net/xintanalabs/rssotto/service/TypeService.kt @@ -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 { + return typeRepository.getAllTypes() + } + + fun findById(id: String): Type? { + return typeRepository.getTypeById(id) + } +} \ No newline at end of file