Test openapi schema response of FindArticlesVersion,GetOneArticle,UpsertArticle
change snack_case to camelCase
This commit is contained in:
@@ -138,11 +138,11 @@ fun Application.module(env: Env = PROD) {
|
||||
|
||||
install(ContentNegotiation) {
|
||||
jackson {
|
||||
propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE
|
||||
propertyNamingStrategy = PropertyNamingStrategies.LOWER_CAMEL_CASE
|
||||
|
||||
registerModule(JodaModule())
|
||||
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
|
||||
configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||
setDefaultPrettyPrinter(
|
||||
DefaultPrettyPrinter().apply {
|
||||
|
||||
@@ -29,7 +29,7 @@ data class ArticleForView(
|
||||
val content: String,
|
||||
val description: String,
|
||||
val tags: List<String> = emptyList(),
|
||||
override val createdBy: CitizenRef,
|
||||
override val createdBy: CitizenCreator,
|
||||
override val versionNumber: Int = 0,
|
||||
override val versionId: UUID = UUID.randomUUID(),
|
||||
val workgroup: WorkgroupCart? = null,
|
||||
@@ -37,7 +37,7 @@ data class ArticleForView(
|
||||
override val draft: Boolean = false,
|
||||
override val deletedAt: DateTime? = null
|
||||
) : ArticleRef(id),
|
||||
ArticleAuthI<CitizenRef>,
|
||||
ArticleAuthI<CitizenCreator>,
|
||||
ArticleWithTitleI,
|
||||
Versionable,
|
||||
CreatedAt by CreatedAt.Imp(),
|
||||
@@ -79,9 +79,10 @@ class ArticleForListing(
|
||||
id: UUID? = null,
|
||||
override val title: String,
|
||||
override val createdBy: CitizenCreator,
|
||||
override val workgroup: WorkgroupCart?,
|
||||
override val deletedAt: DateTime?,
|
||||
override val draft: Boolean
|
||||
override val workgroup: WorkgroupCart? = null,
|
||||
override val deletedAt: DateTime? = null,
|
||||
override val draft: Boolean = false,
|
||||
val lastVersion: Boolean = false
|
||||
) : ArticleForListingI,
|
||||
ArticleRef(id),
|
||||
ArticleAuthI<CitizenCartI>,
|
||||
|
||||
@@ -13,13 +13,13 @@ class ArticleRepository(override var requester: Requester) : RepositoryI {
|
||||
return function.selectOne("id" to id)
|
||||
}
|
||||
|
||||
fun findVersionsById(page: Int = 1, limit: Int = 50, id: UUID): Paginated<ArticleForView> {
|
||||
fun findVersionsById(page: Int = 1, limit: Int = 50, id: UUID): Paginated<ArticleForListing> {
|
||||
return requester
|
||||
.getFunction("find_articles_versions_by_id")
|
||||
.select(page, limit, "id" to id)
|
||||
}
|
||||
|
||||
fun findVersionsByVersionId(page: Int = 1, limit: Int = 50, versionId: UUID): Paginated<ArticleForView> {
|
||||
fun findVersionsByVersionId(page: Int = 1, limit: Int = 50, versionId: UUID): Paginated<ArticleForListing> {
|
||||
return requester
|
||||
.getFunction("find_articles_versions_by_version_id")
|
||||
.select(page, limit, "version_id" to versionId)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.common.dto.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.database.ArticleForListing
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
@@ -37,7 +39,34 @@ object FindArticleVersions {
|
||||
get<ArticleVersionsRequest> {
|
||||
repo.findVersions(it)
|
||||
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||
.let { call.respond(it) }
|
||||
.run {
|
||||
call.respond(
|
||||
toOutput { a: ArticleForListing ->
|
||||
object {
|
||||
val id = a.id
|
||||
val title = a.title
|
||||
val createdBy = object {
|
||||
val id = a.createdBy.id
|
||||
val name = a.createdBy.name.let { n ->
|
||||
object {
|
||||
val firstName = n.firstName
|
||||
val lastName = n.lastName
|
||||
}
|
||||
}
|
||||
val email = a.createdBy.email
|
||||
}
|
||||
val workgroup = a.workgroup?.let { w ->
|
||||
object {
|
||||
val id = w.id
|
||||
val name = w.name
|
||||
}
|
||||
}
|
||||
val draft = a.draft
|
||||
val lastVersion = a.lastVersion
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.common.dto.CreatedAt
|
||||
import fr.dcproject.common.dto.Versionable
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.ArticleViewManager
|
||||
@@ -9,10 +7,6 @@ import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.opinion.dto.Opinionable
|
||||
import fr.dcproject.component.views.dto.Viewable
|
||||
import fr.dcproject.component.views.entity.ViewAggregation
|
||||
import fr.dcproject.component.vote.dto.Votable
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -30,39 +24,56 @@ object GetOneArticle {
|
||||
val article = ArticleRef(article)
|
||||
}
|
||||
|
||||
class Output(
|
||||
article: ArticleForView,
|
||||
views: ViewAggregation = ViewAggregation()
|
||||
) : CreatedAt by CreatedAt.Imp(article),
|
||||
Opinionable by Opinionable.Imp(article),
|
||||
Votable by Votable.Imp(article),
|
||||
Versionable by Versionable.Imp(article),
|
||||
Viewable by Viewable.Imp(views) {
|
||||
val id = article.id
|
||||
val title = article.title
|
||||
val anonymous = article.anonymous
|
||||
val content = article.content
|
||||
val description = article.description
|
||||
val tags = article.tags
|
||||
val draft = article.draft
|
||||
val lastVersion = article.lastVersion
|
||||
val createdBy = article.createdBy
|
||||
val workgroup = article.workgroup?.let { Workgroup(article.workgroup.id, article.workgroup.name) }
|
||||
|
||||
class Workgroup(val id: UUID, val name: String)
|
||||
}
|
||||
|
||||
fun Route.getOneArticle(viewManager: ArticleViewManager<ArticleForView>, ac: ArticleAccessControl, repo: ArticleRepository) {
|
||||
get<ArticleRequest> {
|
||||
val article: ArticleForView = repo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
|
||||
ac.assert { canView(article, citizenOrNull) }
|
||||
|
||||
Output(
|
||||
article,
|
||||
viewManager.getViewsCount(article)
|
||||
).also { out ->
|
||||
call.respond(out)
|
||||
}
|
||||
call.respond(
|
||||
article.let { a ->
|
||||
object {
|
||||
val id = a.id
|
||||
val versionId = a.versionId
|
||||
val versionNumber = a.versionNumber
|
||||
val title = a.title
|
||||
val anonymous = a.anonymous
|
||||
val content = a.content
|
||||
val description = a.description
|
||||
val tags = a.tags
|
||||
val draft = a.draft
|
||||
val lastVersion = a.lastVersion
|
||||
val createdAt = a.createdAt
|
||||
val createdBy: Any = object {
|
||||
val id: UUID = a.createdBy.id
|
||||
val name: Any = object {
|
||||
val firstName: String = a.createdBy.name.firstName
|
||||
val lastName: String = a.createdBy.name.lastName
|
||||
}
|
||||
val email: String = a.createdBy.email
|
||||
}
|
||||
val workgroup: Any? = a.workgroup?.let { w ->
|
||||
object {
|
||||
val id: UUID = w.id
|
||||
val name: String = w.name
|
||||
}
|
||||
}
|
||||
val votes: Any = object {
|
||||
val up: Int = a.votes.up
|
||||
val neutral: Int = a.votes.neutral
|
||||
val down: Int = a.votes.down
|
||||
val total: Int = a.votes.total
|
||||
val score: Int = a.votes.score
|
||||
}
|
||||
val views: Any = viewManager.getViewsCount(article).let { v ->
|
||||
object {
|
||||
val total = v.total
|
||||
val unique = v.unique
|
||||
}
|
||||
}
|
||||
val opinions: Map<String, Int> = a.opinions
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
launch {
|
||||
viewManager.addView(call.request.local.remoteHost, article, citizenOrNull)
|
||||
|
||||
@@ -4,7 +4,6 @@ import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.database.ArticleForUpdate
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input
|
||||
import fr.dcproject.component.auth.citizen
|
||||
@@ -57,9 +56,16 @@ object UpsertArticle {
|
||||
post<UpsertArticleRequest> {
|
||||
val article = call.convertRequestToEntity()
|
||||
ac.assert { canUpsert(article, citizenOrNull) }
|
||||
val newArticle: ArticleForView = repo.upsert(article) ?: error("Article not updated")
|
||||
call.respond(newArticle)
|
||||
publisher.publish(ArticleUpdateNotification(newArticle))
|
||||
repo.upsert(article)?.let { a ->
|
||||
call.respond(
|
||||
object {
|
||||
val id: UUID = a.id
|
||||
val versionId = a.versionId
|
||||
val versionNumber = a.versionNumber
|
||||
}
|
||||
)
|
||||
publisher.publish(ArticleUpdateNotification(a))
|
||||
} ?: error("Article not updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapi: 3.0.2
|
||||
info:
|
||||
version: '0.1'
|
||||
version: ''
|
||||
title: 'DC Project'
|
||||
description: 'A free comunity program for create constitution'
|
||||
|
||||
@@ -46,7 +46,7 @@ paths:
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
created_by:
|
||||
createdBy:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
@@ -56,9 +56,9 @@ paths:
|
||||
name:
|
||||
type: object
|
||||
properties:
|
||||
first_name:
|
||||
firstName:
|
||||
type: string
|
||||
last_name:
|
||||
lastName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
@@ -74,6 +74,71 @@ paths:
|
||||
type: string
|
||||
draft:
|
||||
type: boolean
|
||||
post:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
summary: Create new Article
|
||||
tags:
|
||||
- article
|
||||
operationId: insertArticle
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- title
|
||||
- content
|
||||
- description
|
||||
- tags
|
||||
- anonymous
|
||||
- draft
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
example:
|
||||
Limit power of press
|
||||
content:
|
||||
type: string
|
||||
example:
|
||||
Lorem upsum...
|
||||
description:
|
||||
type: string
|
||||
example:
|
||||
I think is the bether choice
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: [ ]
|
||||
example: [ power, press ]
|
||||
anonymous:
|
||||
type: boolean
|
||||
default: true
|
||||
draft:
|
||||
type: boolean
|
||||
default: false
|
||||
workgroup:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/UuidEntity'
|
||||
- default: null
|
||||
responses:
|
||||
200:
|
||||
description: Article created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
versionId:
|
||||
type: string
|
||||
format: uuid
|
||||
versionNumber:
|
||||
type: integer
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/articles/{article}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/article'
|
||||
@@ -90,53 +155,66 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
anonymous:
|
||||
type: boolean
|
||||
content:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
draft:
|
||||
type: boolean
|
||||
last_version:
|
||||
type: boolean
|
||||
created_by:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
$ref: '#/components/schemas/ArticleResponse'
|
||||
|
||||
/articles/{article}/versions:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/article'
|
||||
get:
|
||||
summary: Get all versions of articles
|
||||
tags:
|
||||
- article
|
||||
operationId: getArticleVersions
|
||||
responses:
|
||||
200:
|
||||
description: The versions of Article
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Paginated'
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: object
|
||||
properties:
|
||||
first_name:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
workgroup:
|
||||
type: object
|
||||
nullable: true
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
draft:
|
||||
type: boolean
|
||||
lastVersion:
|
||||
type: boolean
|
||||
createdBy:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: object
|
||||
properties:
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
workgroup:
|
||||
type: object
|
||||
nullable: true
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
|
||||
components:
|
||||
parameters:
|
||||
@@ -165,7 +243,7 @@ components:
|
||||
name: sort
|
||||
in: query
|
||||
description: The sort field name
|
||||
example: first_name
|
||||
example: firstName
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
@@ -220,6 +298,14 @@ components:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
responses:
|
||||
401:
|
||||
description: Unautorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: noting
|
||||
|
||||
schemas:
|
||||
UUID:
|
||||
type: string
|
||||
@@ -260,6 +346,120 @@ components:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 1
|
||||
ArticleResponse:
|
||||
additionalProperties: false
|
||||
required:
|
||||
- id
|
||||
- versionId
|
||||
- versionNumber
|
||||
- title
|
||||
- anonymous
|
||||
- content
|
||||
- description
|
||||
- tags
|
||||
- draft
|
||||
- lastVersion
|
||||
- createdBy
|
||||
- views
|
||||
- opinions
|
||||
- votes
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
versionId:
|
||||
type: string
|
||||
format: uuid
|
||||
versionNumber:
|
||||
type: integer
|
||||
minimum: 0
|
||||
title:
|
||||
type: string
|
||||
anonymous:
|
||||
type: boolean
|
||||
content:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
draft:
|
||||
type: boolean
|
||||
lastVersion:
|
||||
type: boolean
|
||||
createdAt:
|
||||
type: string
|
||||
format: 'date-time'
|
||||
createdBy:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- email
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: object
|
||||
required:
|
||||
- firstName
|
||||
- lastName
|
||||
properties:
|
||||
firstName:
|
||||
type: string
|
||||
lastName:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
workgroup:
|
||||
type: object
|
||||
nullable: true
|
||||
additionalProperties: false
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
views:
|
||||
additionalProperties: false
|
||||
required:
|
||||
- total
|
||||
- unique
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
unique:
|
||||
type: integer
|
||||
opinions:
|
||||
additionalProperties:
|
||||
type: integer
|
||||
example:
|
||||
'good writed': 5
|
||||
'to short': 19
|
||||
votes:
|
||||
additionalProperties: false
|
||||
required:
|
||||
- up
|
||||
- neutral
|
||||
- down
|
||||
- total
|
||||
- score
|
||||
properties:
|
||||
up:
|
||||
type: integer
|
||||
neutral:
|
||||
type: integer
|
||||
down:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
score:
|
||||
type: integer
|
||||
|
||||
securitySchemes:
|
||||
JWTAuth:
|
||||
|
||||
@@ -13,10 +13,17 @@ begin
|
||||
into resource, total
|
||||
from (
|
||||
select
|
||||
a.*,
|
||||
find_citizen_by_id_with_user(a.created_by_id) as created_by,
|
||||
find_workgroup_by_id(a.workgroup_id) as workgroup,
|
||||
count_vote(a.id) as votes
|
||||
a.id,
|
||||
a.created_at,
|
||||
find_citizen_by_id_with_user(a.created_by_id) as created_by,
|
||||
find_workgroup_by_id(a.workgroup_id) as workgroup,
|
||||
a.version_id,
|
||||
a.version_number,
|
||||
a.title,
|
||||
a.deleted_at,
|
||||
a.draft,
|
||||
a.last_version,
|
||||
count_vote(a.id) as votes
|
||||
from article as a
|
||||
where a.version_id = _version_id
|
||||
order by a.version_number desc
|
||||
|
||||
Reference in New Issue
Block a user