create SQL function for get citizen votes with multiple target ids
add updated_at field on vote table
This commit is contained in:
@@ -31,21 +31,6 @@ open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<
|
|||||||
)!!
|
)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findByCitizen(
|
|
||||||
citizen: CitizenEntity,
|
|
||||||
target: String,
|
|
||||||
typeReference: TypeReference<List<VoteEntity<T>>>,
|
|
||||||
page: Int = 1,
|
|
||||||
limit: Int = 50
|
|
||||||
): Paginated<VoteEntity<T>> =
|
|
||||||
findByCitizen(
|
|
||||||
citizen.id ?: error("The citizen must have an id"),
|
|
||||||
target,
|
|
||||||
typeReference,
|
|
||||||
page,
|
|
||||||
limit
|
|
||||||
)
|
|
||||||
|
|
||||||
fun findByCitizen(
|
fun findByCitizen(
|
||||||
citizenId: UUID,
|
citizenId: UUID,
|
||||||
target: String,
|
target: String,
|
||||||
@@ -61,6 +46,21 @@ open class Vote <T: UuidEntity>(override var requester: Requester): RepositoryI<
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findCitizenVotesByTargets(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
targets: List<UUID>
|
||||||
|
): List<VoteEntity<*>> {
|
||||||
|
val typeReference = object: TypeReference<List<VoteEntity<UuidEntity>>>() {}
|
||||||
|
return requester.run {
|
||||||
|
val citizenId = citizen.id ?: error("The citizen must have an id")
|
||||||
|
getFunction("find_citizen_votes_by_target_ids")
|
||||||
|
.select(typeReference, mapOf(
|
||||||
|
"citizen_id" to citizenId,
|
||||||
|
"ids" to targets
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VoteArticle (requester: Requester): Vote<Article>(requester) {
|
class VoteArticle (requester: Requester): Vote<Article>(requester) {
|
||||||
@@ -70,7 +70,7 @@ class VoteArticle (requester: Requester): Vote<Article>(requester) {
|
|||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<VoteEntity<Article>> =
|
): Paginated<VoteEntity<Article>> =
|
||||||
findByCitizen(
|
findByCitizen(
|
||||||
citizen,
|
citizen.id ?: error("The citizen must have an id"),
|
||||||
"article",
|
"article",
|
||||||
object: TypeReference<List<VoteEntity<Article>>>() {},
|
object: TypeReference<List<VoteEntity<Article>>>() {},
|
||||||
page,
|
page,
|
||||||
@@ -85,7 +85,7 @@ class VoteConstitution (requester: Requester): Vote<Constitution>(requester) {
|
|||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<VoteEntity<Constitution>> =
|
): Paginated<VoteEntity<Constitution>> =
|
||||||
findByCitizen(
|
findByCitizen(
|
||||||
citizen,
|
citizen.id ?: error("The citizen must have an id"),
|
||||||
"constitution",
|
"constitution",
|
||||||
object: TypeReference<List<VoteEntity<Constitution>>>() {},
|
object: TypeReference<List<VoteEntity<Constitution>>>() {},
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import io.ktor.locations.put
|
|||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
|
import java.util.*
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
import fr.dcproject.repository.VoteArticle as VoteArticleRepository
|
import fr.dcproject.repository.VoteArticle as VoteArticleRepository
|
||||||
@@ -33,6 +34,14 @@ object VoteArticlePaths {
|
|||||||
limit: Int = 50,
|
limit: Int = 50,
|
||||||
val search: String? = null
|
val search: String? = null
|
||||||
): PaginatedRequestI by PaginatedRequest(page, limit)
|
): PaginatedRequestI by PaginatedRequest(page, limit)
|
||||||
|
|
||||||
|
@Location("/citizens/{citizen}/votes")
|
||||||
|
class CitizenVotesByIdsRequest(val citizen: Citizen, id: List<String>) {
|
||||||
|
val id: List<UUID> = id
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { UUID.fromString(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
@@ -55,4 +64,11 @@ fun Route.voteArticle(repo: VoteArticleRepository) {
|
|||||||
|
|
||||||
call.respond(votes)
|
call.respond(votes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get<VoteArticlePaths.CitizenVotesByIdsRequest> {
|
||||||
|
val votes = repo.findCitizenVotesByTargets(it.citizen, it.id)
|
||||||
|
assertCan(VIEW, votes)
|
||||||
|
|
||||||
|
call.respond(votes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -62,14 +62,7 @@ paths:
|
|||||||
- citizen
|
- citizen
|
||||||
operationId: getCitizen
|
operationId: getCitizen
|
||||||
parameters:
|
parameters:
|
||||||
- name: citizen
|
- $ref: '#/components/parameters/citizen'
|
||||||
in: path
|
|
||||||
description: ID of citizen
|
|
||||||
example: e74be8e4-6823-47c4-bd1b-789725b2fa8e
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
format: uuid
|
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: The Citizen object
|
description: The Citizen object
|
||||||
@@ -79,6 +72,40 @@ paths:
|
|||||||
$ref: '#/components/schemas/CitizenResponse'
|
$ref: '#/components/schemas/CitizenResponse'
|
||||||
404:
|
404:
|
||||||
description: Citizen not found
|
description: Citizen not found
|
||||||
|
/citizens/{citizen}/votes:
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- JWTAuth: []
|
||||||
|
summary: Get Citizen
|
||||||
|
tags:
|
||||||
|
- citizen
|
||||||
|
operationId: getCitizenVotes
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/citizen'
|
||||||
|
- name: id
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
example:
|
||||||
|
- 1329ab90-edae-cfed-f863-c8cb069fa327
|
||||||
|
- cab54e50-ce85-bba0-da23-fc9f75feeaf5
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The Votes objects
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/VoteResponse'
|
||||||
|
404:
|
||||||
|
description: Citizen not found
|
||||||
|
|
||||||
/citizens:
|
/citizens:
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
@@ -609,6 +636,16 @@ components:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
citizen:
|
||||||
|
name: citizen
|
||||||
|
in: path
|
||||||
|
description: ID of citizen
|
||||||
|
example: 770d46e8-c458-417d-beaf-0f2faa109b26
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
JWTAuth:
|
JWTAuth:
|
||||||
type: http
|
type: http
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
create or replace function find_citizen_votes_by_target_ids(
|
||||||
|
_citizen_id uuid,
|
||||||
|
_ids uuid[],
|
||||||
|
_reference regclass default null,
|
||||||
|
out resource json
|
||||||
|
) language plpgsql as
|
||||||
|
$$
|
||||||
|
begin
|
||||||
|
select
|
||||||
|
json_agg(t)
|
||||||
|
into resource
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
v.*,
|
||||||
|
find_reference_by_id(v.target_id, _reference) as target,
|
||||||
|
find_citizen_by_id(v.created_by_id) as created_by
|
||||||
|
|
||||||
|
from vote as v
|
||||||
|
|
||||||
|
where
|
||||||
|
(_reference is null or _reference = target_reference)
|
||||||
|
and target_id = any(_ids)
|
||||||
|
and created_by_id = _citizen_id
|
||||||
|
|
||||||
|
order by
|
||||||
|
_ids
|
||||||
|
limit 100
|
||||||
|
) as t;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- drop function if exists find_citizen_votes_by_target_ids(uuid, uuid[], regclass);
|
||||||
@@ -7,25 +7,29 @@ begin
|
|||||||
values (_created_by_id, _target_id, _note, _anonymous)
|
values (_created_by_id, _target_id, _note, _anonymous)
|
||||||
on conflict (created_by_id, target_id) do update set
|
on conflict (created_by_id, target_id) do update set
|
||||||
note = excluded.note,
|
note = excluded.note,
|
||||||
anonymous = excluded.anonymous;
|
anonymous = excluded.anonymous,
|
||||||
|
updated_at = now();
|
||||||
elseif reference = 'constitution'::regclass then
|
elseif reference = 'constitution'::regclass then
|
||||||
insert into vote_for_constitution (created_by_id, target_id, note, anonymous)
|
insert into vote_for_constitution (created_by_id, target_id, note, anonymous)
|
||||||
values (_created_by_id, _target_id, _note, _anonymous)
|
values (_created_by_id, _target_id, _note, _anonymous)
|
||||||
on conflict (created_by_id, target_id) do update set
|
on conflict (created_by_id, target_id) do update set
|
||||||
note = excluded.note,
|
note = excluded.note,
|
||||||
anonymous = excluded.anonymous;
|
anonymous = excluded.anonymous,
|
||||||
|
updated_at = now();
|
||||||
elseif reference = 'comment_on_article'::regclass then
|
elseif reference = 'comment_on_article'::regclass then
|
||||||
insert into vote_for_comment_on_article (created_by_id, target_id, note, anonymous)
|
insert into vote_for_comment_on_article (created_by_id, target_id, note, anonymous)
|
||||||
values (_created_by_id, _target_id, _note, _anonymous)
|
values (_created_by_id, _target_id, _note, _anonymous)
|
||||||
on conflict (created_by_id, target_id) do update set
|
on conflict (created_by_id, target_id) do update set
|
||||||
note = excluded.note,
|
note = excluded.note,
|
||||||
anonymous = excluded.anonymous;
|
anonymous = excluded.anonymous,
|
||||||
|
updated_at = now();
|
||||||
elseif reference = 'comment_on_constitution'::regclass then
|
elseif reference = 'comment_on_constitution'::regclass then
|
||||||
insert into vote_for_comment_on_constitution (created_by_id, target_id, note, anonymous)
|
insert into vote_for_comment_on_constitution (created_by_id, target_id, note, anonymous)
|
||||||
values (_created_by_id, _target_id, _note, _anonymous)
|
values (_created_by_id, _target_id, _note, _anonymous)
|
||||||
on conflict (created_by_id, target_id) do update set
|
on conflict (created_by_id, target_id) do update set
|
||||||
note = excluded.note,
|
note = excluded.note,
|
||||||
anonymous = excluded.anonymous;
|
anonymous = excluded.anonymous,
|
||||||
|
updated_at = now();
|
||||||
else
|
else
|
||||||
raise exception '% no implemented', reference::text;
|
raise exception '% no implemented', reference::text;
|
||||||
end if;
|
end if;
|
||||||
|
|||||||
@@ -450,8 +450,9 @@ execute procedure set_comment_parents_ids();
|
|||||||
|
|
||||||
create table vote
|
create table vote
|
||||||
(
|
(
|
||||||
anonymous boolean default true not null,
|
updated_at timestamptz default now() not null check ( updated_at >= created_at ),
|
||||||
note int not null check ( note >= -1 and note <= 1 ),
|
anonymous boolean default true not null,
|
||||||
|
note int not null check ( note >= -1 and note <= 1 ),
|
||||||
foreign key (created_by_id) references citizen (id),
|
foreign key (created_by_id) references citizen (id),
|
||||||
primary key (id),
|
primary key (id),
|
||||||
unique (created_by_id, target_id)
|
unique (created_by_id, target_id)
|
||||||
|
|||||||
@@ -29,3 +29,10 @@ Feature: vote Article
|
|||||||
| limit | 50 |
|
| limit | 50 |
|
||||||
| total | 1 |
|
| total | 1 |
|
||||||
| result[0].note | 1 |
|
| result[0].note | 1 |
|
||||||
|
|
||||||
|
Scenario: Can get votes of current citizen by target ids
|
||||||
|
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
|
||||||
|
When I send a GET request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/votes?id=9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b&id=64b1f265-bfb3-332b-eef9-d00f63a3beaa"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response should contain object:
|
||||||
|
| [0].note | 1 |
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ declare
|
|||||||
$json$;
|
$json$;
|
||||||
votes jsonb;
|
votes jsonb;
|
||||||
votes_of_citizen json;
|
votes_of_citizen json;
|
||||||
|
votes_of_citizen_for_targets json;
|
||||||
votes_total int;
|
votes_total int;
|
||||||
begin
|
begin
|
||||||
-- insert user for context
|
-- insert user for context
|
||||||
@@ -97,6 +98,11 @@ begin
|
|||||||
assert (votes_total = 1), format('votes count for user %s must be 1, instead of %s', _citizen_id, votes_total);
|
assert (votes_total = 1), format('votes count for user %s must be 1, instead of %s', _citizen_id, votes_total);
|
||||||
assert ((votes_of_citizen#>>'{0,note}')::int = -1), format('the note must be -1, instead of %s', (votes_of_citizen#>>'{0,note}'));
|
assert ((votes_of_citizen#>>'{0,note}')::int = -1), format('the note must be -1, instead of %s', (votes_of_citizen#>>'{0,note}'));
|
||||||
|
|
||||||
|
-- test "find_citizen_votes_by_target_ids"
|
||||||
|
select find_citizen_votes_by_target_ids(_citizen_id, array[(created_article->>'id')]::uuid[]) into votes_of_citizen_for_targets;
|
||||||
|
assert (json_array_length(votes_of_citizen_for_targets) = 1), format('the function must be return 1 vote, instead of %s', json_array_length(votes_of_citizen_for_targets));
|
||||||
|
assert (votes_of_citizen_for_targets#>>'{0,target_id}' = created_article->>'id'), format('target_id of vote must be %s, instead of %s', created_article->>'id', votes_of_citizen_for_targets#>>'{0,target_id}');
|
||||||
|
|
||||||
-- delete vote and context
|
-- delete vote and context
|
||||||
delete from vote;
|
delete from vote;
|
||||||
delete from article;
|
delete from article;
|
||||||
|
|||||||
Reference in New Issue
Block a user